JavaScript:async/await的基礎(chǔ)用法

相對于回調(diào)函數(shù)來說,Promise是一種相對優(yōu)雅的選擇。那么有沒有更好的方案呢?答案就是async/await。
優(yōu)勢主要體現(xiàn)在,級聯(lián)調(diào)用,也就是幾個調(diào)用依次發(fā)生的場景。
async/await。被稱為到目前最優(yōu)雅的異步過程解決方案,不知道你是否認(rèn)同,反正我是信了。

相對于Promise,async/await有什么優(yōu)點(diǎn)?

比較場景: 級聯(lián)調(diào)用,也就是幾個調(diào)用依次發(fā)生的場景

  • Promise主要用then函數(shù)的鏈?zhǔn)秸{(diào)用,一直點(diǎn)點(diǎn)點(diǎn),是一種從左向右的橫向?qū)懛ā?br> async/await從上到下,順序執(zhí)行,就像寫同步代碼一樣。這更符合人編寫代碼的習(xí)慣

  • Promise的then函數(shù)只能傳遞一個參數(shù),雖然可以通過包裝成對象,但是這會導(dǎo)致傳遞冗余信息,頻繁的解析又重新組合參數(shù),比較麻煩。
    async/await沒有這個限制,就當(dāng)做普通的局部變量來處理好了,用let或者const定義的塊級變量,想怎么用就怎么用,想定義幾個就定義幾個,完全沒有限制,也沒有冗余的工作。

  • Promise在使用的時候最好將同步代碼和異步代碼放在不同的then節(jié)點(diǎn)中,這樣結(jié)構(gòu)更加清晰。
    async/await整個書寫習(xí)慣都是同步的,不需要糾結(jié)同步和異步的區(qū)別。當(dāng)然,異步過程需要包裝成一個Promise對象,放在await關(guān)鍵字后面,這點(diǎn)還是要牢記的。

  • Promise是根據(jù)函數(shù)式編程的范式,對異步過程進(jìn)行了一層封裝。
    async/await是基于協(xié)程的機(jī)制,是真正的“保存上下文,控制權(quán)切換 ... ... 控制權(quán)恢復(fù),取回上下文”這種機(jī)制,是對異步過程更精確的一種描述。

進(jìn)程、線程和協(xié)程的理解
上面的文章很好地解釋了這幾個概念的區(qū)別。
如果不糾結(jié)細(xì)節(jié),可以簡單地認(rèn)為:進(jìn)程 > 線程 > 協(xié)程;
協(xié)程可以獨(dú)立完成一些與界面無關(guān)的工作,不會阻塞主線程渲染界面,也就是不會卡。
協(xié)程,雖然小一點(diǎn),不過能完成我們程序員交給的任務(wù)。而且我們可以自由控制運(yùn)行和阻塞狀態(tài),不需要求助于高大上的系統(tǒng)調(diào)度,這才是重點(diǎn)。

  • async/await是基于Promise的,是進(jìn)一步的一種優(yōu)化。不過再寫代碼的時候,Promise本身的API出現(xiàn)得很少,很接近同步代碼的寫法。

await關(guān)鍵字使用時有哪些注意點(diǎn)?

  • 只能放在async函數(shù)內(nèi)部使用,不能放在普通函數(shù)里面,否則會報錯。

  • 后面放Promise對象,在Pending狀態(tài)時,相應(yīng)的協(xié)程會交出控制權(quán),進(jìn)入等待狀態(tài)。這個是本質(zhì)。

  • awaitasync wait的意思,wait的是resolve(data)消息,并把數(shù)據(jù)data返回。比如,下面代碼中,當(dāng)Promise對象由Pending變?yōu)?code>Resolved的時候,變量a就等于data;然后再順序執(zhí)行下面的語句console.log(a);
    這真的是等待,真的是順序執(zhí)行,表現(xiàn)和同步代碼幾乎一模一樣。

const a = await new Promise((resolve, reject) => {
    // async process ...
    return resolve(data);
});
console.log(a);
  • await后面也可以跟同步代碼,不過系統(tǒng)會自動轉(zhuǎn)化成一個Promise對象。
    比如
    const a = await 'hello world';
    其實就相當(dāng)于
    const a = await Promise.resolve('hello world');
    這跟同步代碼
    const a = 'hello world';是一樣的,還不如省點(diǎn)事,去掉這里的await關(guān)鍵字。

  • await只關(guān)心異步過程成功的消息resolve(data),拿到相應(yīng)的數(shù)據(jù)data。至于失敗消息reject(error),不關(guān)心,不處理。
    當(dāng)然對于錯誤消息的處理,有以下幾種方法供選擇:
    (1)讓await后面的Promise對象自己catch
    (2)也可以讓外面的async函數(shù)返回的Promise對象統(tǒng)一catch
    (3)像同步代碼一樣,放在一個try...catch結(jié)構(gòu)中

async關(guān)鍵字使用時有哪些注意點(diǎn)?

  • 有了這個async關(guān)鍵字,只是表明里面可能有異步過程,里面可以有await關(guān)鍵字。當(dāng)然,全部是同步代碼也沒關(guān)系。當(dāng)然,這時候這個async關(guān)鍵字就顯得多余了。不是不能加,而是不應(yīng)該加。

  • async函數(shù),如果里面有異步過程,會等待;
    但是async函數(shù)本身會馬上返回,不會阻塞當(dāng)前線程。

可以簡單認(rèn)為,async函數(shù)工作在主線程,同步執(zhí)行,不會阻塞界面渲染。
async函數(shù)內(nèi)部由async關(guān)鍵字修飾的異步過程,工作在相應(yīng)的協(xié)程上,會阻塞等待異步任務(wù)的完成再返回。

  • async函數(shù)的返回值是一個Promise對象,這個是和普通函數(shù)本質(zhì)不同的地方。這也是使用時重點(diǎn)注意的地方
    (1)return newPromise();這個符合async函數(shù)本意;
    (2)return data;這個是同步函數(shù)的寫法,這里是要特別注意的。這個時候,其實就相當(dāng)于Promise.resolve(data);還是一個Promise對象。
    在調(diào)用async函數(shù)的地方通過簡單的=是拿不到這個data的。
    那么怎么樣拿到這個data呢?
    很簡單,返回值是一個Promise對象,用.then(data => { })函數(shù)就可以。
    (3)如果沒有返回,相當(dāng)于返回了Promise.resolve(undefined);

  • await是不管異步過程的reject(error)消息的,async函數(shù)返回的這個Promise對象的catch函數(shù)就負(fù)責(zé)統(tǒng)一抓取內(nèi)部所有異步過程的錯誤。
    async函數(shù)內(nèi)部只要有一個異步過程發(fā)生錯誤,整個執(zhí)行過程就中斷,這個返回的Promise對象的catch就能抓到這個錯誤。

  • async函數(shù)執(zhí)行和普通函數(shù)一樣,函數(shù)名帶個()就可以了,參數(shù)個數(shù)隨意,沒有限制;也需要有async關(guān)鍵字。
    只是返回值是一個Promise對象,可以用then函數(shù)得到返回值,用catch抓去整個流程中發(fā)生的錯誤。

基本套路

Step1:用Promise對象包裝異步過程,這個和Promise的使用一樣。只是參數(shù)個數(shù)隨意,沒有限制。

function sleep(ms) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve('sleep for ' + ms + ' ms');
        }, ms);
    });
}

Step2:定義異步流程,可以將按照需要定制,就像寫同步代碼那樣

async function asyncFunction() {
    console.time('asyncFunction total executing:');
    const sleep1 = await sleep(2000);
    console.log('sleep1: ' + sleep1);
    const [sleep2, sleep3, sleep4]= await Promise.all([sleep(2000), sleep(1000), sleep(1500)]);
    console.log('sleep2: ' + sleep2);
    console.log('sleep3: ' + sleep3);
    console.log('sleep4: ' + sleep4);
    const sleepRace = await Promise.race([sleep(3000), sleep(1000), sleep(1000)]);
    console.log('sleep race: ' + sleepRace);
    console.timeEnd('asyncFunction total executing:');
    
    return 'asyncFunction done.'  // 這個可以不返回,這里只是做個標(biāo)記,為了顯示流程
}

Step3:像普通函數(shù)調(diào)用async函數(shù),在then函數(shù)中獲取整個流程的返回信息,在catch函數(shù)統(tǒng)一處理出錯信息

asyncFunction().then(data => {
    console.log(data);       // asyncFunction return 的內(nèi)容在這里獲取
}).catch(error => {
    console.log(error);      // asyncFunction 的錯誤統(tǒng)一在這里抓取
});

console.log('after asyncFunction code executing....'); // 這個代表asyncFunction函數(shù)后的代碼,
                                                       // 顯示asyncFunction本身會立即返回,不會阻塞主線程

流程解析

上面的代碼執(zhí)行之后,輸出的log如下,顯示了代碼執(zhí)行流程

after asyncFunction code executing....
sleep1: sleep for 2000 ms
sleep2: sleep for 2000 ms
sleep3: sleep for 1000 ms
sleep4: sleep for 1500 ms
sleep race: sleep for 1000 ms
asyncFunction total executing:: 5006.276123046875ms
asyncFunction done.
  1. after asyncFunction code executing....代碼位置在async函數(shù)asyncFunction()調(diào)用之后,反而先輸出。這說明async函數(shù)asyncFunction()調(diào)用之后會馬上返回,不會阻塞主線程。

  2. sleep1: sleep for 2000 ms這是第一個await之后的第一個異步過程,最先執(zhí)行,也最先完成,說明后面的代碼,不論是同步和異步,都在等他執(zhí)行完畢。

  3. sleep2 ~ sleep4這是第二個await之后的Promise.all()異步過程。這是“比慢模式”,三個sleep都完成后,再運(yùn)行下面的代碼,耗時最長的是2000ms

  4. sleep race: sleep for 1000 ms這是第三個await之后的Promise.race()異步過程。這是“比快模式”,耗時最短sleep都完成后,就運(yùn)行下面的代碼。耗時最短的是1000ms

  5. asyncFunction total executing:: 5006.276123046875ms這是最后的統(tǒng)計總共運(yùn)行時間代碼。三個await之后的異步過程之和
    1000(獨(dú)立的) + 2000(Promise.all) + 1000(Promise.race) = 5000ms
    這個和統(tǒng)計出來的5006.276123046875ms非常接近。說明上面的異步過程,和同步代碼執(zhí)行過程一致,協(xié)程真的是在等待異步過程執(zhí)行完畢。

  6. asyncFunction done.這個是async函數(shù)返回的信息,在執(zhí)行時的then函數(shù)中獲得,說明整個流程完畢之后參數(shù)傳遞的過程。

log.png

異常處理

  • async標(biāo)注過的函數(shù),返回一個Promise對象,采用.then().catch()的方式來進(jìn)行異常處理,是非常自然的方法,也推薦這么做。就像上面的step3那樣做。

  • 另外一種方法,就是對于異步過程采用await關(guān)鍵字,采用同步的try{} catch(){}的方式來進(jìn)行異常處理。

  • 這里要注意的是await關(guān)鍵字只能用在async標(biāo)注的函數(shù)中,所以,原來的函數(shù),不管以前是同步的還是異步的,都要加上async關(guān)鍵字,比如componentDidMount()就要變?yōu)?code>async componentDidMount()才可以在內(nèi)部使用await關(guān)鍵字,不過功能上沒有任何影響。

  • 另外,采用同步的try{} catch(){}的方式,可以把同步,異步代碼都可以放在里面,有錯誤都能抓到,比如null.length這種,也能抓到。

async componentDidMount() { // 這是React Native的回調(diào)函數(shù),加個async關(guān)鍵字,沒有任何影響,但是可以用await關(guān)鍵字
    // 將異步和同步的代碼放在一個try..catch中,異常都能抓到
    try {
        let array = null;
        let data = await asyncFunction();  // 這里用await關(guān)鍵字,就能拿到結(jié)果值;否則,沒有await的話,只能拿到Promise對象
        if (array.length > 0) {  // 這里會拋出異常,下面的catch也能抓到
            array.push(data);
        }
    } catch (error) {
        alert(JSON.stringify(error))
    }
}

這里模擬的是網(wǎng)絡(luò)過程。一般情況,array是一個數(shù)組,用if (array.length > 0)判斷一下長度,有值再處理,沒有問題。但是,一旦網(wǎng)絡(luò)出問題,array就是一個null,平時工作很好的if (array.length > 0)判斷就會拋異常,JS代碼就中斷,停止工作,會帶來意想不到的問題。
這里加了一個try..catch結(jié)構(gòu),這種異常就能捕獲,(這是同步代碼中的異常,不能用.then().catch()抓到),根據(jù)異常信息,一般是null沒有length屬性,方便定位問題。這里的話用if (array && (array.length > 0))就會安全一點(diǎn)。

參考文章

本文只是介紹了async/await一種基礎(chǔ)的用法。一個例子,將三種Promise使用中常用的場景模式都包括進(jìn)去了,并且代碼風(fēng)格和同步代碼非常相似。相比之下async/await這套異步代碼編程方式確實比較優(yōu)雅。

下面幾篇文章都非常不錯,介紹了async/await很多靈活的用法,建議好好看看。

理解 JavaScript 的 async/await

深入理解ES7的async/await

async 函數(shù)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 異步編程對JavaScript語言太重要。Javascript語言的執(zhí)行環(huán)境是“單線程”的,如果沒有異步編程,根本...
    呼呼哥閱讀 7,399評論 5 22
  • 簡單介紹下這幾個的關(guān)系為方便起見 用以下代碼為例簡單介紹下這幾個東西的關(guān)系, async 在函數(shù)聲明前使用asyn...
    _我和你一樣閱讀 21,476評論 1 24
  • Promise的含義: ??Promise是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和...
    呼呼哥閱讀 2,262評論 0 16
  • 譯者按: Node.js的異步編程方式有效提高了應(yīng)用性能;然而回調(diào)地獄卻讓人望而生畏,Promise讓我們告別回調(diào)...
    Fundebug閱讀 2,932評論 0 43
  • 接著上節(jié) condition_varible ,本節(jié)主要介紹future的內(nèi)容,練習(xí)代碼地址。本文參考http:/...
    jorion閱讀 15,033評論 1 5

友情鏈接更多精彩內(nèi)容