天天看點

談談promise/async/await的執行順序與V8引擎的BUG

故事還是要從下面這道面試題說起:請問下面這段代碼的輸出是什麼?

console.log("script start");

async function async1() {
    await async2();
    console.log("async1 end");
}

async function async2() {
    console.log("async2 end");
}
async1();

setTimeout(function() {
    console.log("setTimeout");
}, 0);

new Promise(resolve => {
    console.log("Promise");
    resolve();
})
    .then(function() {
        console.log("promise1");
    })
    .then(function() {
        console.log("promise2");
    });

console.log("script end");           

複制

題目和答案

上述代碼中,在

Chrome 66

node v10

中,正确輸出是:

script start
async2 end
Promise
script end
promise1
promise2
async1 end
setTimeout           

複制

注意:在新版本的浏覽器中,

await

輸出順序被“提前”了,請看官耐心慢慢看。

流程解釋

邊看輸出結果,邊做解釋吧:

  1. 正常輸出

    script start

  2. 執行

    async1

    函數,此函數中又調用了

    async2

    函數,輸出

    async2 end

    。回到

    async1

    函數,遇到了

    await

    ,讓出線程。
  3. 遇到

    setTimeout

    ,扔到下一輪宏任務隊列
  4. 遇到

    Promise

    對象,立即執行其函數,輸出

    Promise

    。其後的

    resolve

    ,被扔到了微任務隊列
  5. 正常輸出

    script end

  6. 此時,此次

    Event Loop

    宏任務都執行完了。來看下第二步被扔進來的微任務,因為

    async2

    函數是

    async

    關鍵詞修飾,是以,将

    await async2

    後的代碼扔到微任務隊列中
  7. 執行第 4 步被扔到微任務隊列的任務,輸出

    promise1

    promise2

  8. 執行第 6 步被扔到微任務隊列的任務,輸出

    async1 end

  9. 第一輪 EventLoop 完成,執行第二輪 EventLoop。執行

    setTimeout

    中的回調函數,輸出

    setTimeout

再談 async 和 await

細心的朋友肯定會發現前面第 6 步,如果

async2

函數是沒有

async

關鍵詞修飾的一個普通函數呢?

// 新的async2函數
function async2() {
    console.log("async2 end");
}           

複制

輸出結果如下所示:

script start
async2 end
Promise
script end
async1 end
promise1
promise2
setTimeout           

複制

不同的結果就出現在前面所說的第 6 步:如果 await 函數後面的函數是普通函數,那麼其後的微任務就正常執行;否則,會将其再放入微任務隊列。

其實是 V8 引擎的 BUG

看到前面,正常人都會覺得真奇怪!(但是按照上面的訣竅倒也是可以了解)

然而 V8 團隊确定了這是個 bug(很多強行解釋要被打臉了),具體的 PR請看這裡。好在,這個問題已經在最新的 Chrome 浏覽器中被修複了。

簡單點說,前面兩段不同代碼的運作結果都是:

script start
async2 end
Promise
script end
async1 end
promise1
promise2
setTimeout           

複制

await

就是讓出線程,其後的代碼放入微任務隊列(不會再多一次放入的過程),就這麼簡單了。

掃碼關注「心譚部落格」, 專注前端與算法

目前已有前端面試、劍指Offer·JS、動畫設計、Webpack等系列專題