如果你已經知道了js中存在宏任務和微任務,那么你一定已經了解過promise了。因為在js中promise是微任務的一個入口。
先來看一道題:
setTimeout(function(){
console.log('setTimeout')
});
new Promise(function(resolve, reject){
console.log('pormise body');
resolve();
}).then(function(){
console.log('promise then')
});
console.log('main');
這題的答案是:
promise body
main
promise then
setTimeout
promise body出現在第一行一點也不意外,意外的是,setTimeout出現在了promise then的后邊。
setTimeout和promise then都是異步調用,setTimeout又定義在promise之前,如果沒有意外,應該是先輸出setTimeout才對,但這里卻恰恰相反。
這里涉及到的知識點,就是宏任務與微任務了。標記一下上邊的代碼:
setTimeout(function(){ // 同步代碼,語句1
console.log('setTimeout') // 宏任務,語句2
});
new Promise(function(resolve, reject){
console.log('pormise body'); // 同步代碼,語句3
resolve(); // 同步代碼,語句4
}).then(function(){
console.log('promise then') // 微任務,語句5
});
console.log('main'); // 同步代碼,語句6
那么他們運行的規則是怎樣的呢?
原來,宏任務與微任務,都各自有一個調用隊列(先進先出)。
遇到宏任務,微任務,則將他們推入各自的調用隊列。需要注意的是,同步代碼也是宏任務。
宏任務執行完一個,則去清空微任務隊列,微任務清空后再去執行下一個宏任務。
這個過程頗像去醫院診室排隊看大夫的情景:
如果有一個病人A叫到號以后,又被大夫安排先去做檢查,那么做完檢查以后病人A回到診室門口,可以直接等待當前正在看病的病人B與大夫交談結束后,將檢查結果交給大夫,而不用再次排隊。
我們來把上面的代碼一行一行解讀一下:
首先定義兩個隊列,宏任務隊列:MacroQqueue, 微任務隊列: MicroQueue
第一步,先按同步代碼順序運行
同步代碼,語句1: 添加一個宏任務,將語句2推入MacroQueue。 // MacroQueue: [{task: 語句2}]
同步代碼,語句3: 打印promise body
同步代碼,語句4: 添加一個微任務,將語句5推入MicroQueue。 // MicroQueue: [{task: 語句5}]
同步代碼,語句6: 打印main。 // 同步代碼(宏任務)完成
第二步,開始清空微任務隊列
微任務: 語句5,從MicroQueue跳出,打印promise then。 // 微任務隊列全部清空
第三步,開始清空宏任務隊列
宏任務:語句2,從MacroQqueue跳出,打印setTimeout // 宏任務隊列全部清空
第四步:開始清空微任務隊列
隊列為空...
一輪循環完成。開始下一輪,如此循環下去。
通過上面的講解,大家應該能對宏任務,微任務的運行機制有了一定的了解了吧。那么都有哪些常見的宏任務與微任務呢?
請看下表:
宏任務 | 瀏覽器 | nodejs |
---|---|---|
同步代碼 | ✅ | ✅ |
I/O | ✅ | ✅ |
setTimeout | ✅ | ✅ |
setInterval | ✅ | ✅ |
setImmediate | ❌ | ✅ |
requestAnimationFrame | ✅ | ❌ |
微任務 | 瀏覽器 | nodejs |
---|---|---|
process.nextTick | ❌ | ✅ |
MutationObserver | ✅ | ❌ |
Promise (async/await) | ✅ | ✅ |
好了,我們來一道復雜一點的題練練手:
console.log('sync statement 1');
Promise.resolve().then(function() {
console.log('micro task 1');
setTimeout(function() {
console.log('macro task 1');
}, 0);
}).then(function() {
console.log('micro task 2');
});
setTimeout(function() {
console.log('macro task 2')
Promise.resolve().then(function(){
console.log('micro task 3');
})
}, 0)
console.log('sync statement 2');
//輸出:
// sync statement 1
// sync statement 2
// micro task 1
// micro task 2
// macro task 2
// micro task 3
// macro task 1
標注一下方便大家分析:
console.log('sync statement 1'); // 同步代碼,語句1
Promise.resolve().then(function() { // 同步代碼,語句2,注冊了一個微任務
console.log('micro task 1'); // 微任務,語句3
setTimeout(function() { // 微任務,語句4,同時注冊了一個宏任務
console.log('macro task 1'); // 宏任務,語句5
}, 0);
}).then(function() {
console.log('micro task 2'); // 微任務,語句6
});
setTimeout(function() { // 同步代碼,語句7
console.log('macro task 2') // 宏任務,語句8
Promise.resolve().then(function(){ // 宏任務,語句9,同時注冊了一個微任務
console.log('micro task 3'); // 微任務,語句10
})
}, 0)
console.log('sync statement 2'); // 同步代碼,語句11