任務隊列
首先我們要知道關於JavaScript的一些規則:
- JavaScript是被設計成單線程的
- JavaScript的任務分爲同步任務和異步任務
同步任務都在主線程上執行,形成一個執行棧。當主線程執行完之後,運行微任務(micro-task)隊列的任務直到爲空,更新UI渲染(會根據瀏覽器的邏輯,決定要不要馬上執行更新),然後再運行宏任務(macro-task)隊列的任務直到爲空......流程如下:
(主線程上的執行棧同步任務,可以視爲是第一個macro-task隊列)
macro-task -> micro-task(如果存在) -> 更新UI渲染
如此無限循環上面的流程,是爲JavaScript的Event Loop機制。
宏任務
宏任務(macro-task),宏任務隊列可以有一個或者多個。每個任務都有一個任務源(task source),源自同一個任務源的 task 必須放到同一個任務隊列,從不同源來的則被添加到不同隊列。
宏任務:script(全局任務), setTimeout, setInterval, setImmediate, I/O, UI rendering.
微任務
微任務(micro-task),微任務在渲染更新前,macro-task之後執行。
關於async和await,因爲async await 本身就是promise+generator的語法糖。所以await後面的代碼是microtask。實際上await是一個讓出線程的標誌。await後面的表達式會先執行一遍,將await後面的代碼加入到microtask中,然後就會跳出整個async函數來執行後面的代碼。
微任務:process.nextTick, Promise, Object.observer, MutationObserver,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');
流程如下:
- console打印script start
- setTimeout,是異步宏任務,進入macro-task setTimeout隊列
- async1(), async await函數,在await之前是同步任務,直接執行,打印async1 start
- await async2(),await後面的表達式會先執行一遍,打印async2
- await 下面的代碼視爲promise.then,進入micro-task promise隊列,跳出async1()
- new Promise,promise內,.then之前的代碼是直接執行的,所以打印promise1
- .then內函數進入micro-task promise隊列後
- console,直接打印script end
- 主線程執行棧運行完並清空了,micro-task進入執行棧,分別按順序執行打印async1 end 和 promise2。
- micro-task隊列清空,macro-task進入執行棧,打印setTimeout,程序運行完畢。
完整結果如下:
/**
*script start
*async1 start
*async2
*promise1
*script end
*async1 end
*promise2
*setTimeout
*/
該結果基於chrome 版本 72.0.3626.121。因爲async await標準有所改變,所以稍老版本的瀏覽器結果可能不一致。
參考:
https://jakearchibald.com/201...
https://github.com/Advanced-F...