JS中的async和await


  前言

 異步編程允許我們在執行一個長時間任務時,程序不需要進行等待,而是繼續執行之后的代碼,直到這些任務完成之后再回來通知你。早期的異步編程是通過回調函數實現的,這種編程的模式避免了程序的阻塞,大大提高了CPU的執行效率,尤其適用於一些前后端數據庫交互的操作。然而回調函數會出現回調地獄的情況,為了解決這一問題,ES6 出現了 Promise。那為何 ES7 又會出現 async/await 呢?它對比 Promise 又有什么優勢呢?那么接下來就讓我們一起來看看吧!

 

  什么是async/await?

 async/await 是編寫異步代碼的新方式,是基於 Promise 實現的。

 async/await 使得異步代碼看起來像同步代碼,這正是它的魔力所在。

 

  async

 我們可以使用 async 來聲明一個函數為異步函數,如下:

  async function fn1(){
        return 88;
    }
    async function fn2(){
        return Promise.resolve(66);
    }
    console.log('result1:',fn1());
    console.log('result2:',fn2());

  

  從打印的結果中我們可以看出執行一個 async 函數,返回的都是 Promise 對象。如果返回的是像函數 fn1 中那樣普通的值的話,它會幫你封裝成一個 Promise 對象。

 

  await 在等什么?

 現在我們已經知道 async 是來聲明一個函數為異步函數的,那么 await 又是干什么的呢?我們繼續先上一段代碼看看:

    async function fn3(){
        const p3 = Promise.resolve(66);

        p3.then(data => {
            console.log('data of then:',data);
        })

        const data = await p3;
        console.log('data of await:',data);
    }
    fn3();

  

 看了打印結果后,我們知道了,await等的是結果,promise.then 成功的情況對應 await。也就是 await 可以獲取 Promise 函數中 resolve 或 reject 的值。注意 await 關鍵字只能放在 async 函數內部。如果用在普通函數就會報錯。

 

  async/await 的執行順序

 可能大家都知道 await 會讓出線程,阻塞后面的代碼,那么下面例子中, 'async2' 和 'script start' 誰先打印呢?

 是從左向右執行,一旦碰到await直接跳出, 阻塞async2()的執行?

 還是從右向左,先執行async2后,發現有await關鍵字,於是讓出線程,阻塞代碼呢?

    async function async1() {
        console.log( 'async1 start' )
        await async2()
        console.log( 'async1 end' )
    }
    async function async2() {
        console.log( 'async2' )
    }
    async1()
    console.log( 'script start' )

  

從打印結果可以看出,從右向左的。先打印async2,后打印的script start

 之所以提一嘴,是因為我經常看到這樣的說法,「一旦遇到await就立刻讓出線程,阻塞后面的代碼」

 這樣的說法,會讓我誤以為,await后面那個函數, async2()也直接被阻塞呢。

await 等到之后,做了一件什么事情?

 那么右側表達式的結果,就是await要等的東西。

 等到之后,對於await來說,分2個情況

  • 不是promise對象
  • 是promise對象

如果不是 promise , await會阻塞后面的代碼,先執行async外面的同步代碼,同步代碼執行完,再回到async內部,把這個非promise的東西,作為 await 表達式的結果

如果它等到的是一個 promise 對象,await 也會暫停 async 后面的代碼,先執行async外面的同步代碼,等着 Promise 對象 fulfilled,然后把 resolve 的參數作為 await 表達式的運算結果。

 

  try...catch 捕獲異常

 捕獲異常是使用 try...catch 的方式來處理,因為 await 后面跟着的是 Promise 對象,當有異常的情況下會被 Promise 對象的內部 catch 捕獲,而 await 就是一個 then 的語法糖,並不會捕獲異常, 那就需要使用 try...catch 來捕獲異常,並進行相應的邏輯處理。

    async function fn5(){
        const p5 = Promise.reject('捕獲異常');
        try{
            const data5 = await p5;
            console.log('data5:',data5);
        }catch (e){
            console.log('e',e);
        }
    }
    fn5()

 

  async/await 和 Promise 對比

 用 setTimeout 模擬耗時的異步操作,先來看看 Promise 怎么寫:

    function takeLongTime() {
        return new Promise(resolve => {
            setTimeout(() => resolve("long_time_value"), 1000);
        });
    }
    takeLongTime().then(v => {
        console.log("got", v);
    });

 改用 async/await 寫:

    function takeLongTime() {
        return new Promise(resolve => {
            setTimeout(() => resolve("long_time_value"), 1000);
        });
    }
    async function test(){
        const a = await takeLongTime();
        console.log(a);
    }
    test();

 一個疑問產生了,這兩段代碼,兩種方式對異步調用的處理(實際就是對 Promise 對象的處理)差別並不明顯,甚至使用 async/await 還需要多寫一些代碼,那它的優勢到底在哪?

async/await 的優勢在於處理 then 鏈

 單一的 Promise 鏈並不能發現 async/await 的優勢,但是,如果需要處理由多個 Promise 組成的 then 鏈的時候,優勢就能體現出來了(很有意思,Promise 通過 then 鏈來解決多層回調的問題,現在又用 async/await 來進一步優化它)。

 假設一個業務,分多個步驟完成,每個步驟都是異步的,而且依賴於上一個步驟的結果。我們仍然用 setTimeout 來模擬異步操作:

/**
 * 傳入參數 n,表示這個函數執行的時間(毫秒)
 * 執行的結果是 n + 200,這個值將用於下一步驟
 */
function takeLongTime(n) {
    return new Promise(resolve => {
        setTimeout(() => resolve(n + 200), n);
    });
}

function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}

function step2(n) {
    console.log(`step2 with ${n}`);
    return takeLongTime(n);
}

function step3(n) {
    console.log(`step3 with ${n}`);
    return takeLongTime(n);
}

 現在用 Promise 方式來實現這三個步驟的處理:

function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => step2(time2))
        .then(time3 => step3(time3))
        .then(result => {
            console.log(`result is ${result}`);
            console.timeEnd("doIt");
        });
}

doIt();

// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms

 如果用 async/await 來實現呢,會是這樣:

async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time2);
    const result = await step3(time3);
    console.log(`result is ${result}`);
    console.timeEnd("doIt");
}

doIt();

 

 

 

 


 

參考資料:https://segmentfault.com/a/1190000007535316

https://www.cnblogs.com/fundebug/p/10095355.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM