摘要: 面試必問javascript
Fundebug經受權轉載,版權歸原做者全部。前端
說實話,關於js的異步執行順序,宏任務、微任務這些,或者async/await這些慨念已經有很是多的文章寫了。java
可是怎麼說呢,簡單來講,業務中不多用async,不太懂async呢。node
研究了一天,感受懂了,所手癢想寫一篇 ,哈哈。es6
畢竟本身學會的知識,若是連表達清楚都作不到,怎麼能期望本身用好它呢?面試
因此我寫這個的文章,主要仍是交流學習,若是您已經清楚了eventloop/async/await/promise這些東西呢,能夠 break 啦segmentfault
有說的不對的地方,歡迎留言討論,promise
那麼仍是先經過一道題自我檢測一下,是否有必要繼續看下去把。瀏覽器
其實呢,這是去年一道爛大街的「今日頭條」的面試題。異步
我以爲這道題的關鍵,不只是說出正確的打印順序,更重要的可否說清楚每個步驟,爲何這樣執行。
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");
注:由於是一道前端面試題,因此答案是以瀏覽器的eventloop機制爲準的,在node平臺上運行會有差別。
script start async1 start async2 promise1 script end promise2 async1 end setTimeout
若是你發現運行結果跟本身想的同樣,能夠選擇跳過這篇文章啦,
或者若是你有興趣看看俺倆的理解有沒有區別,能夠跳到後面的 「畫圖講解的部分」
不過若是是對 ES7 的 async 不太熟悉,是不要緊的哈,由於這篇文章會詳解 async。
那麼若是不具有這些知識呢,推薦幾篇我以爲講得比較清楚的文章
我推薦的那篇文章,對 async/await 講得更詳細。不過我但願本身能更加精煉的幫你理解它們這部分,主要會講解 3 點內容
帶 async 關鍵字的函數,它使得你的函數的返回值一定是 promise 對象
也就是
若是async關鍵字函數返回的不是promise,會自動用Promise.resolve()包裝
若是async關鍵字函數顯式地返回promise,那就以你返回的promise爲準
這是一個簡單的例子,能夠看到 async 關鍵字函數和普通函數的返回值的區別
async function fn1(){ return 123 } function fn2(){ return 123 } console.log(fn1()) console.log(fn2()) Promise {<resolved>: 123} 123
因此你看,async 函數也沒啥了不得的,之後看到帶有 async 關鍵字的函數也不用慌張,你就想它無非就是把return值包裝了一下,其餘就跟普通函數同樣。
關於async關鍵字還有那些要注意的?
await等的是右側「表達式」的結果
也就是說,
右側若是是函數,那麼函數的return值就是「表達式的結果」
右側若是是一個 'hello' 或者什麼值,那表達式的結果就是 'hello'
async function async1() { console.log( 'async1 start' ) await async2() console.log( 'async1 end' ) } async function async2() { console.log( 'async2' ) } async1() console.log( 'script start' )
這裏注意一點,可能你們都知道await會讓出線程,阻塞後面的代碼,那麼上面例子中, 'async2' 和 'script start' 誰先打印呢?
是從左向右執行,一旦碰到await直接跳出, 阻塞async2()的執行?
仍是從右向左,先執行async2後,發現有await關鍵字,因而讓出線程,阻塞代碼呢?
實踐的結論是,從右向左的。先打印async2,後打印的script start
之因此提一嘴,是由於我常常看到這樣的說法,「一旦遇到await就馬上讓出線程,阻塞後面的代碼」
這樣的說法,會讓我誤覺得,await後面那個函數, async2()也直接被阻塞呢。
那麼右側表達式的結果,就是await要等的東西。
等到以後,對於await來講,分2個狀況
若是不是 promise , await會阻塞後面的代碼,先執行async外面的同步代碼,同步代碼執行完,再回到async內部,把這個非promise的東西,做爲 await表達式的結果
若是它等到的是一個 promise 對象,await 也會暫停async後面的代碼,先執行async外面的同步代碼,等着 Promise 對象 fulfilled,而後把 resolve 的參數做爲 await 表達式的運算結果。
咱們以開篇的經典面試題爲例,分析這個例子中的宏任務和微任務。
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");
先分享一個我我的理解的宏任務和微任務的慨念,在我腦海中宏任務和爲微任務如圖所示
也就是「宏任務」、「微任務」都是隊列。
一段代碼執行時,會先執行宏任務中的同步代碼,
下面就以面試題爲例子,分析這段代碼的執行順序。
每次宏任務和微任務發生變化,我都會畫一個圖來表示他們的變化。
// 首先是2個函數聲明,雖然有async關鍵字,但不是調用咱們就不看。而後首先是打印同步代碼 console.log('script start')
默認<script></script>所包裹的代碼,其實能夠理解爲是第一個宏任務,因此這裏是宏任務2
咱們說過看到帶有async關鍵字的函數,不用懼怕,它的僅僅是把return值包裝成了promise,其餘並無什麼不一樣的地方。因此就很普通的打印 console.log( 'async1 start' )
前文提過await,1.它先計算出右側的結果,2.而後看到await後,中斷async函數
目前就直接打印 console.log('async2')
執行new Promise(),Promise構造函數是直接調用的同步代碼,因此 console.log( 'promise1' )
代碼運行到promise.then(),發現這個是微任務,因此暫時不打印,只是推入當前宏任務的微任務隊列中。
注意:這裏只是把promise2推入微任務隊列,並無執行。微任務會在當前宏任務的同步代碼執行完畢,纔會依次執行
沒什麼好說的。執行完這個同步代碼後,「async外的代碼」終於走了一遍
下面該回到 await 表達式那裏,執行await Promise.resolve(undefined)了
這部分可能不太好理解,我儘可能表達個人想法。
對於 await Promise.resolve(undefined) 如何理解呢?
根據 MDN 原話咱們知道
若是一個 Promise 被傳遞給一個 await 操做符,await 將等待 Promise 正常處理完成並返回其處理結果。
在咱們這個例子中,就是Promise.resolve(undefined)正常處理完成,並返回其處理結果。那麼await async2()就算是執行結束了。
目前這個promise的狀態是fulfilled,等其處理結果返回就能夠執行await下面的代碼了。
那什麼時候能拿處處理結果呢?
回憶平時咱們用promise,調用resolve後,什麼時候能拿處處理結果?是否是須要在then的第一個參數裏,才能拿到結果。
(調用resolve時,會把then的參數推入微任務隊列,等主線程空閒時,再調用它)
因此這裏的 await Promise.resolve() 就相似於
Promise.resolve(undefined).then((undefined) => { })
把then的第一個回調參數 (undefined) => {} 推入微任務隊列。
then執行完,纔是await async2()執行結束。
await async2()執行結束,才能繼續執行後面的代碼
如圖
此時當前宏任務1都執行完了,要處理微任務隊列裏的代碼。
微任務隊列,先進選出的原則,
可是微任務2執行後,await async2()語句結束,後面的代碼再也不被阻塞,因此打印
console.log('async1 end')
宏任務2的執行比較簡單,就是打印
console.log('setTimeout')
谷歌瀏覽器,目前是版本是「版本 71.0.3578.80(正式版本) (64 位)」 Mac操做系統
Safari瀏覽器的測試結果
火狐瀏覽器的測試結果
若是不理解能夠留言,有錯誤的話也歡迎指正。
Fundebug經受權轉載,版權歸原做者全部。
說實話,關於js的異步執行順序,宏任務、微任務這些,或者async/await這些慨念已經有很是多的文章寫了。
可是怎麼說呢,簡單來講,業務中不多用async,不太懂async呢。
研究了一天,感受懂了,所手癢想寫一篇 ,哈哈。
畢竟本身學會的知識,若是連表達清楚都作不到,怎麼能期望本身用好它呢?
因此我寫這個的文章,主要仍是交流學習,若是您已經清楚了eventloop/async/await/promise這些東西呢,能夠 break 啦
有說的不對的地方,歡迎留言討論,
那麼仍是先經過一道題自我檢測一下,是否有必要繼續看下去把。
其實呢,這是去年一道爛大街的「今日頭條」的面試題。
我以爲這道題的關鍵,不只是說出正確的打印順序,更重要的可否說清楚每個步驟,爲何這樣執行。
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");
注:由於是一道前端面試題,因此答案是以瀏覽器的eventloop機制爲準的,在node平臺上運行會有差別。
script start async1 start async2 promise1 script end promise2 async1 end setTimeout
若是你發現運行結果跟本身想的同樣,能夠選擇跳過這篇文章啦,
或者若是你有興趣看看俺倆的理解有沒有區別,能夠跳到後面的 「畫圖講解的部分」
不過若是是對 ES7 的 async 不太熟悉,是不要緊的哈,由於這篇文章會詳解 async。
那麼若是不具有這些知識呢,推薦幾篇我以爲講得比較清楚的文章
我推薦的那篇文章,對 async/await 講得更詳細。不過我但願本身能更加精煉的幫你理解它們這部分,主要會講解 3 點內容
帶 async 關鍵字的函數,它使得你的函數的返回值一定是 promise 對象
也就是
若是async關鍵字函數返回的不是promise,會自動用Promise.resolve()包裝
若是async關鍵字函數顯式地返回promise,那就以你返回的promise爲準
這是一個簡單的例子,能夠看到 async 關鍵字函數和普通函數的返回值的區別
async function fn1(){ return 123 } function fn2(){ return 123 } console.log(fn1()) console.log(fn2()) Promise {<resolved>: 123} 123
因此你看,async 函數也沒啥了不得的,之後看到帶有 async 關鍵字的函數也不用慌張,你就想它無非就是把return值包裝了一下,其餘就跟普通函數同樣。
關於async關鍵字還有那些要注意的?
await等的是右側「表達式」的結果
也就是說,
右側若是是函數,那麼函數的return值就是「表達式的結果」
右側若是是一個 'hello' 或者什麼值,那表達式的結果就是 'hello'
async function async1() { console.log( 'async1 start' ) await async2() console.log( 'async1 end' ) } async function async2() { console.log( 'async2' ) } async1() console.log( 'script start' )
這裏注意一點,可能你們都知道await會讓出線程,阻塞後面的代碼,那麼上面例子中, 'async2' 和 'script start' 誰先打印呢?
是從左向右執行,一旦碰到await直接跳出, 阻塞async2()的執行?
仍是從右向左,先執行async2後,發現有await關鍵字,因而讓出線程,阻塞代碼呢?
實踐的結論是,從右向左的。先打印async2,後打印的script start
之因此提一嘴,是由於我常常看到這樣的說法,「一旦遇到await就馬上讓出線程,阻塞後面的代碼」
這樣的說法,會讓我誤覺得,await後面那個函數, async2()也直接被阻塞呢。
那麼右側表達式的結果,就是await要等的東西。
等到以後,對於await來講,分2個狀況
若是不是 promise , await會阻塞後面的代碼,先執行async外面的同步代碼,同步代碼執行完,再回到async內部,把這個非promise的東西,做爲 await表達式的結果
若是它等到的是一個 promise 對象,await 也會暫停async後面的代碼,先執行async外面的同步代碼,等着 Promise 對象 fulfilled,而後把 resolve 的參數做爲 await 表達式的運算結果。
咱們以開篇的經典面試題爲例,分析這個例子中的宏任務和微任務。
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");
先分享一個我我的理解的宏任務和微任務的慨念,在我腦海中宏任務和爲微任務如圖所示
也就是「宏任務」、「微任務」都是隊列。
一段代碼執行時,會先執行宏任務中的同步代碼,
下面就以面試題爲例子,分析這段代碼的執行順序。
每次宏任務和微任務發生變化,我都會畫一個圖來表示他們的變化。
// 首先是2個函數聲明,雖然有async關鍵字,但不是調用咱們就不看。而後首先是打印同步代碼 console.log('script start')
默認<script></script>所包裹的代碼,其實能夠理解爲是第一個宏任務,因此這裏是宏任務2
咱們說過看到帶有async關鍵字的函數,不用懼怕,它的僅僅是把return值包裝成了promise,其餘並無什麼不一樣的地方。因此就很普通的打印 console.log( 'async1 start' )
前文提過await,1.它先計算出右側的結果,2.而後看到await後,中斷async函數
目前就直接打印 console.log('async2')
執行new Promise(),Promise構造函數是直接調用的同步代碼,因此 console.log( 'promise1' )
代碼運行到promise.then(),發現這個是微任務,因此暫時不打印,只是推入當前宏任務的微任務隊列中。
注意:這裏只是把promise2推入微任務隊列,並無執行。微任務會在當前宏任務的同步代碼執行完畢,纔會依次執行
沒什麼好說的。執行完這個同步代碼後,「async外的代碼」終於走了一遍
下面該回到 await 表達式那裏,執行await Promise.resolve(undefined)了
這部分可能不太好理解,我儘可能表達個人想法。
對於 await Promise.resolve(undefined) 如何理解呢?
根據 MDN 原話咱們知道
若是一個 Promise 被傳遞給一個 await 操做符,await 將等待 Promise 正常處理完成並返回其處理結果。
在咱們這個例子中,就是Promise.resolve(undefined)正常處理完成,並返回其處理結果。那麼await async2()就算是執行結束了。
目前這個promise的狀態是fulfilled,等其處理結果返回就能夠執行await下面的代碼了。
那什麼時候能拿處處理結果呢?
回憶平時咱們用promise,調用resolve後,什麼時候能拿處處理結果?是否是須要在then的第一個參數裏,才能拿到結果。
(調用resolve時,會把then的參數推入微任務隊列,等主線程空閒時,再調用它)
因此這裏的 await Promise.resolve() 就相似於
Promise.resolve(undefined).then((undefined) => { })
把then的第一個回調參數 (undefined) => {} 推入微任務隊列。
then執行完,纔是await async2()執行結束。
await async2()執行結束,才能繼續執行後面的代碼
如圖
此時當前宏任務1都執行完了,要處理微任務隊列裏的代碼。
微任務隊列,先進選出的原則,
可是微任務2執行後,await async2()語句結束,後面的代碼再也不被阻塞,因此打印
console.log('async1 end')
宏任務2的執行比較簡單,就是打印
console.log('setTimeout')
谷歌瀏覽器,目前是版本是「版本 71.0.3578.80(正式版本) (64 位)」 Mac操做系統
Safari瀏覽器的測試結果
火狐瀏覽器的測試結果
若是不理解能夠留言,有錯誤的話也歡迎指正。