目錄
同步&異步的概念
js中異步的應(yīng)用場景
實(shí)現(xiàn)異步的四種方法
1、 回調(diào)函數(shù)
2、Promise
3、Generator
4、 async/await
????????「異步編程」是前端工程師日常開發(fā)中經(jīng)常會用到的技術(shù),也是校招面試過程中??嫉囊粋€(gè)知識點(diǎn)。
????????通過掌握「異步編程」的四種方式,可以讓我們能夠更好地處理JavaScript中的異步操作,提高代碼的性能和用戶體驗(yàn)。
????????因此,「今天就想和大家來聊聊JS異步編程的四種方式!」
同步&異步的概念
在講這四種異步方案之前,我們先來明確一下同步和異步的概念:
????????所謂「同步(synchronization)」,簡單來說,就是「順序執(zhí)行」,指的是同一時(shí)間只能做一件事情,只有目前正在執(zhí)行的事情做完之后,才能做下一件事情。
????????「同步操作的優(yōu)點(diǎn)」在于做任何事情都是依次執(zhí)行,井然有序,不會存在大家同時(shí)搶一個(gè)資源的問題。
????????「同步操作的缺點(diǎn)」在于「會阻塞后續(xù)代碼的執(zhí)行」。如果當(dāng)前執(zhí)行的任務(wù)需要花費(fèi)很長的時(shí)間,那么后面的程序就只能一直等待。
????????所謂「異步(Asynchronization)」,指的是當(dāng)前代碼的執(zhí)行不影響后面代碼的執(zhí)行。當(dāng)程序運(yùn)行到異步的代碼時(shí),會將該異步的代碼作為任務(wù)放進(jìn)「任務(wù)隊(duì)列」,而不是推入主線程的調(diào)用棧。等主線程執(zhí)行完之后,再去任務(wù)隊(duì)列里執(zhí)行對應(yīng)的任務(wù)即可。
????????因此,「異步操作的優(yōu)點(diǎn)就是:不會阻塞后續(xù)代碼的執(zhí)行?!?/strong>
js中異步的應(yīng)用場景
開篇講了同步和異步的概念,那么在JS中異步的應(yīng)用場景有哪些呢?
-
「定時(shí)任務(wù)」:setTimeout、setInterval
-
「網(wǎng)絡(luò)請求」:ajax請求、動態(tài)創(chuàng)建img標(biāo)簽的加載
-
「事件監(jiān)聽器」:addEventListener
實(shí)現(xiàn)異步的四種方法
????????對于「setTimeout、setInterval、addEventListener」這種異步場景,不需要我們手動實(shí)現(xiàn)異步,直接調(diào)用即可。
????????但是對于「ajax請求」、「node.js中操作數(shù)據(jù)庫這種異步」,就需要我們自己來實(shí)現(xiàn)了~
1、 回調(diào)函數(shù)
在微任務(wù)隊(duì)列出現(xiàn)之前,JS實(shí)現(xiàn)異步的主要方式就是通過「回調(diào)函數(shù)」。
以一個(gè)簡易版的Ajax請求為例,代碼結(jié)構(gòu)如下所示:
function?ajax(obj){
?let?default?=?{
???url:?'...',
???type:'GET',
???async:true,
???contentType:?'application/json',
???success:function(){}
????};
?for?(let?key?in?obj)?{
????????defaultParam[key]?=?obj[key];
????}
????let?xhr;
????if?(window.XMLHttpRequest)?{
????????xhr?=?new?XMLHttpRequest();
????}?else?{
????????xhr?=?new?ActiveXObject('Microsoft.XMLHTTP');
????}
????
????xhr.open(defaultParam.type,?defaultParam.url+'?'+dataStr,?defaultParam.async);
????xhr.send();
????xhr.onreadystatechange?=?function?(){
????????if?(xhr.readyState?===?4){
????????????if(xhr.status?===?200){
????????????????let?result?=?JSON.parse(xhr.responseText);
????????????????//?在此處調(diào)用回調(diào)函數(shù)
????????????????defaultParam.success(result);
????????????}
????????}
????}
}
我們在業(yè)務(wù)代碼里可以這樣調(diào)用「ajax請求」:
ajax({
???url:'#',
???type:GET,
???success:function(e){
????//?回調(diào)函數(shù)里就是對請求結(jié)果的處理
???}
});
????????「ajax請求」中的success方法就是一個(gè)回調(diào)函數(shù),回調(diào)函數(shù)中執(zhí)行的是我們請求成功之后要做的進(jìn)一步操作。
????????這樣就初步實(shí)現(xiàn)了異步,但是回調(diào)函數(shù)有一個(gè)非常嚴(yán)重的缺點(diǎn),那就是「回調(diào)地獄」的問題。
????????大家可以試想一下,如果我們在回調(diào)函數(shù)里再發(fā)起一個(gè)ajax請求呢?那豈不是要在success函數(shù)里繼續(xù)寫一個(gè)ajax請求?那如果需要多級嵌套發(fā)起ajax請求呢?豈不是需要多級嵌套?
如果嵌套的層級很深的話,我們的代碼結(jié)構(gòu)可能就會變成這樣:
????????因此,為了解決回調(diào)地獄的問題,提出了「promise」、「async/await」、「generator」的概念。
2、Promise
「Promise」作為典型的微任務(wù)之一,它的出現(xiàn)可以使JS達(dá)到異步執(zhí)行的效果。
一個(gè)「Promise函數(shù)」的結(jié)構(gòu)如下列代碼如下:
const?promise?=?new?Promise((resolve,?reject)?=>?{
?resolve('a');
});
promise
????.then((arg)?=>?{?console.log(`執(zhí)行resolve,參數(shù)是${arg}`)?})
????.catch((arg)?=>?{?console.log(`執(zhí)行reject,參數(shù)是${arg}`)?})
????.finally(()?=>?{?console.log('結(jié)束promise')?});
????????如果我們需要嵌套執(zhí)行異步代碼,相比于回調(diào)函數(shù)來說,「Promise」的執(zhí)行方式如下列代碼所示:
const?promise?=?new?Promise((resolve,?reject)?=>?{
?resolve(1);
});
promise.then((value)?=>?{
?????console.log(value);
?????return?value?*?2;
????}).then((value)?=>?{
?????console.log(value);
?????return?value?*?2;
????}).then((value)?=>?{
????console.log(value);
????}).catch((err)?=>?{
??console.log(err);
????});
即通過then來實(shí)現(xiàn)多級嵌套(「鏈?zhǔn)秸{(diào)用」),這看起來是不是就比回調(diào)函數(shù)舒服多了~
每個(gè)「Promise」都會經(jīng)歷的生命周期是:
-
進(jìn)行中(pending)?:此時(shí)代碼執(zhí)行尚未結(jié)束,所以也叫未處理的(unsettled)
已處理(settled)?:異步代碼已執(zhí)行結(jié)束 已處理的代碼會進(jìn)入兩種狀態(tài)中的一種:-
已拒絕(rejected):遇到錯(cuò)誤,異步代碼執(zhí)行失敗 ,由reject()觸發(fā)
-
已完成(fulfilled):表明異步代碼執(zhí)行成功,由resolve()觸發(fā)
-
因此,「pending」,「fulfilled」,?「rejected」就是「Promise」中的三種狀態(tài)啦~
????????需要注意的是,在「Promise」中,要么包含resolve()?來表示?「Promise」?的狀態(tài)為fulfilled,要么包含 reject()?來表示「Promise」的狀態(tài)為rejected。
????????不然我們的「Promise」就會一直處于pending的狀態(tài),直至程序崩潰...
除此之外,「Promise」不僅很好的解決了鏈?zhǔn)秸{(diào)用的問題,它還有很多高頻的操作:
-
·Promise.all(promises)?:接收一個(gè)包含多個(gè)Promise對象的數(shù)組,等待所有都完成時(shí),返回存放它們結(jié)果的數(shù)組。如果任一被拒絕,則立即拋出錯(cuò)誤,其他已完成的結(jié)果會被忽略
-
·Promise.allSettled(promises)?: 接收一個(gè)包含多個(gè)Promise對象的數(shù)組,等待所有都已完成或者已拒絕時(shí),返回存放它們結(jié)果對象的數(shù)組。每個(gè)結(jié)果對象的結(jié)構(gòu)為{status:'fulfilled' // 或 'rejected', value // 或reason}
-
·Promise.race(promises)?: 接收一個(gè)包含多個(gè)Promise對象的數(shù)組,等待第一個(gè)有結(jié)果(完成/拒絕)的Promise,并把其result/error作為結(jié)果返回
示例代碼如下所示:
function?getPromises(){
????return?[
????????new?Promise(((resolve,?reject)?=>?setTimeout(()?=>?resolve(1),?1000))),
????????new?Promise(((resolve,?reject)?=>?setTimeout(()?=>?reject(new?Error('2')),?2000))),
????????new?Promise(((resolve,?reject)?=>?setTimeout(()?=>?resolve(3),?3000))),
????];
}
Promise.all(getPromises()).then(console.log);
Promise.allSettled(getPromises()).then(console.log);
Promise.race(getPromises()).then(console.log);
打印結(jié)果為:
3、Generator
????????「generator」是ES6提出的一種異步編程的方案。因?yàn)槭謩觿?chuàng)建一個(gè)iterator十分麻煩,因此ES6推出了「generator」,用于更方便的創(chuàng)建iterator。
????????也就是說,「generator」就是一個(gè)返回值為iterator對象的函數(shù)。
在講「generator」之前,我們先來看看iterator是什么:
?iterator中文名叫「迭代器」。它為js中各種不同的數(shù)據(jù)結(jié)構(gòu)(Object、Array、Set、Map)提供統(tǒng)一的訪問機(jī)制。
?
任何數(shù)據(jù)結(jié)構(gòu)只要部署iterator接口,就可以完成遍歷操作。
因此iterator也是一種對象,不過相比于普通對象來說,它有著專為迭代而設(shè)計(jì)的接口。
我們通過一個(gè)例子來看看generator的特征:
function*?createIterator()?{
??yield?1;
??yield?2;
??yield?3;
}
//?generators可以像正常函數(shù)一樣被調(diào)用,不同的是會返回一個(gè)?iterator
let?iterator?=?createIterator();
console.log(iterator.next().value);?//?1
console.log(iterator.next().value);?//?2
console.log(iterator.next().value);?//?3
形式上,「generator」?函數(shù)是一個(gè)普通函數(shù),但是有兩個(gè)特征:
-
·function關(guān)鍵字與函數(shù)名之間有一個(gè)星號
-
·函數(shù)體內(nèi)部使用yield語句,定義不同的內(nèi)部狀態(tài)
????????在普通函數(shù)中,我們想要一個(gè)函數(shù)最終的執(zhí)行結(jié)果,一般都是return出來,或者以return作為結(jié)束函數(shù)的標(biāo)準(zhǔn)。運(yùn)行函數(shù)時(shí)也不能被打斷,期間也不能從外部再傳入值到函數(shù)體內(nèi)。
????????但在「generator」中,就打破了這幾點(diǎn),所以「generator」和普通的函數(shù)完全不同。
????????當(dāng)以function* ?的方式聲明了一個(gè)「generator」生成器時(shí),內(nèi)部是可以有許多狀態(tài)的,以yield進(jìn)行斷點(diǎn)間隔。期間我們執(zhí)行調(diào)用這個(gè)生成的「generator」,他會返回一個(gè)遍歷器對象,用這個(gè)對象上的方法,實(shí)現(xiàn)獲得一個(gè)yield后面輸出的結(jié)果。
function*?generator()?{
????yield?1
????yield?2
};
let?iterator?=?generator();
iterator.next()??//?{value:?1,?done:?false}
iterator.next()??//?{value:?2,?done:?false}
iterator.next()??//?{value:?undefined,?done:?true}
4、 async/await
最后我們來講講「async/await」,終于講到這兒了?。?!
「async/await」是ES7提出的關(guān)于異步的終極解決方案。我看網(wǎng)上關(guān)于「async/await」是誰的語法糖這塊有兩個(gè)版本:
-
第一個(gè)版本說「async/await」是Generator的語法糖
-
第二個(gè)版本說「async/await」是Promise的語法糖
其實(shí),這兩種說法都沒有錯(cuò)。
「關(guān)于async/await是Generator的語法糖:」
????????所謂generator語法糖,表明的就是「aysnc/await」實(shí)現(xiàn)的就是generator實(shí)現(xiàn)的功能。但是「async/await」比generator要好用。因?yàn)間enerator執(zhí)行yield設(shè)下的斷點(diǎn)采用的方式就是不斷的調(diào)用iterator方法,這是個(gè)手動調(diào)用的過程。
????????而async配合await得到的就是斷點(diǎn)執(zhí)行后的結(jié)果。因此「async/await」比generator使用更普遍。
「關(guān)于async/await是Promise的語法糖:」
如果不使用「async/await」的話,Promise就需要通過鏈?zhǔn)秸{(diào)用來依次執(zhí)行then之后的代碼:
function?counter(n){
return?new?Promise((resolve,?reject)?=>?{?
??? ?resolve(n?+?1);
????});
}
function?adder(a,?b){
????return?new?Promise((resolve,?reject)?=>?{?
?? ??resolve(a?+?b);
????});
}
function?delay(a){
????return?new?Promise((resolve,?reject)?=>?{?
?? ??setTimeout(()?=>?resolve(a),?1000);
????});
}
//?鏈?zhǔn)秸{(diào)用寫法
function?callAll(){
????counter(1)
???????.then((val)?=>?adder(val,?3))
???????.then((val)?=>?delay(val))
???????.then(console.log);
}
callAll();//5
雖然相比于回調(diào)地獄來說,鏈?zhǔn)秸{(diào)用確實(shí)順眼多了。但是其呈現(xiàn)仍然略繁瑣了一些。
而「async/await的出現(xiàn),就使得我們可以通過同步代碼來達(dá)到異步的效果」:文章來源:http://www.zghlxwxcb.cn/news/detail-802460.html
async?function?callAll(){
???const?count?=?await?counter(1);
???const?sum?=?await?adder(count,?3);
???console.log(await?delay(sum));
}
callAll();//?5
由此可見,「Promise搭配async/await的使用才是正解!」文章來源地址http://www.zghlxwxcb.cn/news/detail-802460.html
到了這里,關(guān)于詳解JS的四種異步解決方案!的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!