所謂的爭論起源於一道面試題chrome
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function() {
console.log('setTimeout')
}, 0)
async1()
new Promise(function(resolve) {
console.log('promise1')
resolve()
}).then(function() {
console.log('promise2')
})
console.log('script end')
複製代碼
我認爲這其中的關鍵的點在於 async
函數中的 await
後面的代碼什麼時候執行segmentfault
因此我按照本身的理解寫下了本身的答案promise
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
複製代碼
可是放到 chrome (71.0.3578.80)下面跑一下,結果和個人想有點出入瀏覽器
script start
async1 start
async2
promise1
script end
promise2
async1 end
setTimeout
複製代碼
先說明個人答案的思路實際上是這樣的,由於 async
函數返回的是一個 promise
,再加上原文裏面的解釋,因此可是我認爲 async
函數能夠用改寫成如下代碼網絡
function async1() {
console.log('2 async1 start')
return Promise.resolve(async2()).then(res => {
console.log('6 async1 end')
})
}
function async2() {
console.log('3 async2')
}
console.log('1 script start')
setTimeout(function() {
console.log('8 setTimeout')
}, 0)
async1()
new Promise(function(resolve) {
console.log('4 promise1')
resolve()
}).then(function() {
console.log('7 promise2')
})
console.log('5 script end')
複製代碼
因此上述結果就是這樣異步
1 script start
2 async1 start
3 async2
4 promise1
5 script end
6 async1 end
7 promise2
8 setTimeout
複製代碼
區別就在於 promise2
和 async1 end
的順序,個人答案是 先 async1 end
後 promise2
,當時瀏覽器的答案卻與我相反。本着 但願本身學習理解的東西能清楚地表達出來的精神,開始了 打破砂鍋問到底的 搜尋資料之旅。async
首先在評論區找到這篇文章 更快的異步函數和 Promise,發佈時間是 2018-11-12,算是比較新的, 英語原文 v8.dev/blog/fast-a…函數
這裏面其實大概講的是 async
函數的實現和優化發展和 promise
之間的聯繫oop
裏面有一句話,和一個 demo
從 Node.js 8(V8 v6.2 / Chrome 62)開始已經徹底支持異步函數,而且從 Node.js 10(V8 v6.8 / Chrome 68)開始已經徹底支持異步迭代器和生成器!
按照文章裏面的說法,在Node.js 8(V8 v6.2 / Chrome 62)中
// wrong
after:await
tick:a
tick:b
複製代碼
這樣的順序是一個 bug,並非規範,而 Node.js 10(V8 v6.8 / Chrome 68)實現了正確的行爲
// correct
tick:a
tick:b
after:await
複製代碼
而後我使用的 chrome 71 跑了一些,確實是如上所說,after:await
在最後打印
而後通過下面的一輪解釋,結論就是由於
在每一個
await
(in ES2017) 引擎必須建立兩個額外的 Promise(即便右側已是一個 Promise)而且它須要至少三個 microtask 隊列 ticks
到這裏,按照個人理解,就是一個 await p;
須要建立兩個 額外的 promise
,p
自己是一個 promise
,因此一行 await p;
就有三個 promise
, 而 console.log('after:await')
是在 await p;
的三個 promise
後執行的 因此我認爲能夠改寫成如下代碼來模擬執行
const p = Promise.resolve();
(async () => {
// p 就是原來 await 右側的執行結果
return p
.then(() => {
// 額外建立的第一個 promise
})
.then(() => {
// 額外建立的第二個 promise
})
.then(() => {
// 這裏是原來 await p; 後面的代碼
console.log('after:await')
})
})()
p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'))
複製代碼
輸出結果
tick:a
tick:b
after:await
複製代碼
那若是我把原代碼擴展一下,變成這樣呢
const p = Promise.resolve();
(async () => {
await p
console.log('after:await')
})()
p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'))
.then(() => console.log('tick:c'))
.then(() => console.log('tick:d'))
複製代碼
根據我理解的 event loop
機制和上面的小結,應該能夠改寫成
const p = Promise.resolve();
(async () => {
// p 就是原來 await 右側的執行結果
return p
.then(() => {
// 額外建立的第一個 promise
})
.then(() => {
// 額外建立的第二個 promise
})
.then(() => {
// 這裏是原來 await p; 後面的代碼
console.log('after:await')
})
})()
p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'))
.then(() => console.log('tick:c'))
.then(() => console.log('tick:d'))
複製代碼
能夠發現都是打印一樣的答案,符合預期
tick:a
tick:b
after:await
tick:c
tick:d
複製代碼
有的人可能不太理解爲何 after:await
會插在中間,能夠參考這篇文章:理解 promise--一個問題引起的思考
可是!
在原貼評論下方有大佬指出說在最新的規範裏這樣的實現是錯的,而且在 chrome 73 已經實現
那麼咱們就把原題在 chrome 73 下的控制檯跑一下,打開個人 chrome canary 瀏覽器
script start
async1 start
async2
promise1
script end
async1 end // 先
promise2 // 後
setTimeout
複製代碼
能夠看到,結果和在 chrome 71 的時候是不一樣的,而後我驚奇地發現這個和我最開始的改寫的答案是一致的, 到這裏我還覺得最新的規範和個人想法是一致的。
咱們接着往那篇文章下面看
下面大概就是說,這樣一個 await
要額外建立出兩個 promise
開銷很大,因此就進行了優化之旅 而最終優化的結果就是
若是傳遞給
await
的值已是一個Promise
,那麼這種優化避免了再次建立Promise
包裝器, 在這種狀況,咱們從最少三個 microtick 到只有一個 microtick 這種行爲相似於 Node.js 8 所作的,可是如今它再也不是一個 bug 它如今是一個正在標準化的優化
也就是說,當初認爲在 Node.js 8(V8 v6.2 / Chrome 62)的 await 行爲是一個 bug 的行爲,通過優化,變成不在是一個 bug,而是一個正在標準化的優化。 那通過優化,目前最新規範的 await
行爲其實就是和 Node.js 8(V8 v6.2 / Chrome 62 的時候同樣,也就是以下代碼的結果同樣,可是性能卻更高
const p = Promise.resolve();
(async () => {
await p; console.log('after:await')
})()
p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'))
// 這樣的結果反而纔是正確的
// 在 chrome 73 下也是這樣的結果,說明 chrome 73 確實已經實現了
after:await
tick:a
tick:b
複製代碼
雖然行爲倒退了和 Chrome 62 的時候同樣,但倒是通過優化使得性能提高的作的改動 而在 Node.js 中,也在將來的 Node 12 中採起了這樣的實現
也就是應該是能夠改寫成
const p = Promise.resolve();
(async () => {
return p.then(() => {
console.log('after:await')
})
})()
p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'))
// 結果
after:await
tick:a
tick:b
複製代碼
如今回到原來的題,我認爲個人改寫是有誤的,通過一番探索以後 我認爲按照 await
(in ES2017) 的實現,能夠改寫成下面這樣
function async1(){
console.log('2 async1 start')
return async2()
.then(() => {
}).then(() => {
}).then(() => {
console.log('6 async1 end')
})
}
function async2(){
console.log('3 async2')
// 這個函數返回一個 promise,若是不返回 promise,則應該在 async1 函數裏面給 async2 函數的執行結果包一層 Promise
return Promise.resolve(undefined)
}
console.log('1 script start')
setTimeout(function(){
console.log('8 setTimeout')
},0)
async1()
new Promise(function(resolve){
console.log('4 promise1')
resolve();
}).then(function(){
console.log('7 promise2')
})
console.log('5 script end')
// 結果
1 script start
2 async1 start
3 async2
4 promise1
5 script end
7 promise2
6 async1 end
8 setTimeout
複製代碼
能夠看到 6 和 7 的順序反了,符合 async
函數在舊規範下的行爲
而根據優化後的新規範,能夠改寫成
function async1(){
console.log('2 async1 start')
return async2().then(() => {
console.log('6 async1 end')
})
}
function async2(){
console.log('3 async2')
// 這個函數返回一個 promise,若是不返回 promise,則應該在 async1 函數裏面給 async2 函數的執行結果包一層 Promise
return Promise.resolve(undefined)
}
console.log('1 script start')
setTimeout(function(){
console.log('8 setTimeout')
},0)
async1()
new Promise(function(resolve){
console.log('4 promise1')
resolve();
}).then(function(){
console.log('7 promise2')
})
console.log('5 script end')
// 結果
1 script start
2 async1 start
3 async2
4 promise1
5 script end
6 async1 end
7 promise2
8 setTimeout
複製代碼
能夠看到 6 和 7 的順序正確,符合 async
函數在新規範下的行爲
在文章的末尾總結是
因爲兩個重要的優化,咱們使異步函數更快:
- 刪除兩個額外的 microtick
- 和去除了 throwaway promise
此補丁還沒有合併到 ECMAScript 規範中。一旦咱們確保此改變不會破壞網絡,咱們的計劃就是立刻執行
而我認爲
async
的執行機制對 本身加深這門語言是有必定積極做用的async
的具體實現仍是不一樣的