前言
最近在準備面試題,console的輸出順序之前一直迷迷糊糊。
必備知識
JS是單執行緒的
單執行緒是 JavaScript 核心特徵之一。這意味著,在 JS 中所有任務都需要排隊執行,前一個任務結束,才會執行後一個任務。
所以這就造成了一個問題:如果前一個任務耗時很長,後一個任務就不得不一直等著前面的任務執行完才能執行。比如我們向伺服器請求一段資料,由於網路問題,可能需要等待 60 秒左右才能成功返回資料,此時只能等待請求完成,JS 才能去處理後面的程式碼。
同步任務和非同步任務
為了解決JS單執行緒帶來的問題,JavaScript 就將所有任務分成了同步任務和非同步任務。
同步任務(Synchronous)
同步任務指的是當前一個(如果有)任務執行完畢,接下來可以立即執行的任務。這些任務將在主執行緒上依次排隊執行。也就是說排排隊
//for(){} 和 console.log() 將會依次執行,最終輸出 0 1 2 3 4 done。
for (let i = 0; i < 5; i++) {
console.log(i)
}
console.log('done')
非同步任務(Asynchronous)
非同步任務相對於同步任務,指的是不需要進入主執行緒排隊執行,而是進入超車道、並車道。也就是任務佇列中,形成一系列的任務。這些任務只有當被通知可以執行的時候,該任務才會重新進入主執行緒執行。
//下面的 then() 方法需要等待 Promise 被 resolve() 之後才能執行,它是一個非同步任務。最終輸出 1 3 2。
console.log(1)
Promise.resolve().then(() => {
console.log(2)
})
console.log(3)
具體來說就是,所有同步任務會在主執行緒上依次排隊執行,形成一個執行棧(Execution Context
Stack)。主執行緒之外,還存在一個任務佇列。當非同步任務有了執行結果,會在任務佇列之中放置對應的事件。當執行棧中的所有同步任務執行完畢,任務佇列裡的非同步任務就會進入執行棧,然後繼續依次執行。
非同步任務(任務佇列)可以分為
macrotasks(taskQueue):宏任務 task,也是我們常說的任務佇列
macrotasks 的劃分:(注意先後順序!)
- (1)setTimeout(延遲呼叫)
- (2)setInterval(間歇呼叫)
- (3)setImmediate(Node 的立即呼叫)
- (4)requestAnimationFrame(高頻的 RAF)
- (5)I/O(I/O 操作)
- (6)UI rendering(UI 渲染)
- (7) 包裹在一個 script 標籤中的 js 程式碼也是一個 Macrotasks
注意: (1)每一個 macrotask 的回撥函式要放在下一車的開頭去執行! (2)只有 setImmediate 能夠確保在下一輪事件迴圈立即得到處理
microtasks:微任務(也稱 job)排程在當前指令碼執行結束後,立即執行的任務,以避免付出額外一個 task 的費用。
microtasks :(注意先後順序!)
- (1)process.nextTick(Node 中 定義出一個動作,並且讓這個動作在下一個事件輪詢的時間點上執行)
- (2)Promises(詳情看這篇文章:www.jianshu.com/p/06d16ce41…
- (3)Object.observe(原生觀察者實現,已廢棄)
- (4)MutationObserver(監聽 DOM change) 只有在 nextTick 空了才處理其它 microtask。(Next tick queue has even higher priority
over the Other Micro tasks queue.)
一個事件迴圈(eventLoop)的執行順序(非常重要):
- ① 開始執行指令碼。
- ② 取 macrotasks(taskQueue)中的第一個 task 執行,該 task 的回撥函式 放在下一個 task 開頭 執行。
- ③ 取 microtasks 中的全部 microtask 依次執行,當這些 microtask 執行結束後,可繼續新增 microtask 繼續執行,直到 microtask 佇列為空。
- ④ 取 macrotasks(taskQueue)中的第二個 task 執行,該 task 的回撥函式 放在下一個 task 開頭 執行。
- ⑤ 再取 microtasks 中的全部 microtask 依次執行,當這些 microtask 執行結束後,可繼續新增 microtask 繼續執行,直到 microtask 佇列為空。
- ⑥ 迴圈 ② ③ 直到 macrotasks、microtasks 為空。
Promise 之所以無法使用 catch 捕獲 setTimeout 回撥中的錯誤,是因為 Promise 的 then/catch 是在 setTimeout 之前執行的。
事件迴圈的順序,決定了 JavaScript 程式碼的執行順序。它從 script (整體程式碼) 開始第一次迴圈。之後全域性上下文進入函式呼叫棧。直到呼叫棧清空 (只剩全域性),然後執行所有的 microtasks。當所有可執行的
microtasks 執行完畢之後。迴圈再次從 macrotasks 開始,找到其中一個任務佇列執行完畢,然後再執行所有的 microtasks,這樣一直迴圈下去。
翻譯過來就是,先執行 Microtasks queue 中的所有 Microtasks,再挑一個 Macrotasks queue 來執行其中所有 Macrotasks,然後繼續執行 Microtasks queue 中的所有
Microtasks,再挑一個 Macrotasks queue 來執行其中所有 Macrotasks ……
這也就解釋了,為什麼同一個事件迴圈中的 Microtasks 會比 Macrotasks 先執行。
習題1
console.log(1)
setTimeout(()=>{
console.log(2)
},0)
process.nextTick(()=>{
console.log(3)
})
new Promise((resolve)=>{
console.log(4)
resolve()
}).then(()=>{
console.log(5)
})
參考 前端進階面試題詳細解答
習題1解析
第一輪事件迴圈
主流程 | Macro event queue | Micro event queue |
---|---|---|
console.log(1) | setTimeout | process |
console.log(4) | then |
- 首先執行同步任務,按出現順序,輸出 1
- 遇到 setTimeout,放入Macro event queue
- 遇到 process,放入 Micro event queue
- 遇到 promise,先立即執行,輸出 4,並將 then 回撥放入 Micro event queue
- 然後看 Micro event queue,逐個執行,輸出 3, 輸出 5
- 第一輪 Event Loop 執行結束
第二輪事件迴圈
主流程 | Macro event queue | Micro event queue |
---|---|---|
setTimeout | ||
- 取出 Macro event queue 第一個放入主流程執行
- 輸出 2
- Micro event queue 沒有任務
- 第二輪 Event Loop 執行結束
習題2
console.log(1)
setTimeout(() => {
console.log(2)
}, 0)
process.nextTick(() => {
console.log(3)
})
new Promise((resolve) => {
console.log(4)
resolve()
}).then(() => {
console.log(5)
})
setTimeout(() => {
console.log(6)
}, 0)
new Promise((resolve) => {
console.log(7)
setTimeout(() => {
console.log(8)
resolve()
}, 0)
}).then(() => {
console.log(9)
setTimeout(() => {
console.log(10)
new Promise((resolve) => {
console.log(11)
resolve()
}).then(() => {
console.log(12)
})
}, 0)
})
// 1, 4, 7, 3, 5, 2, 6, 8, 9, 10, 11, 12
習題2解析
第一輪事件迴圈
主流程 | Macro event queue | Micro event queue |
---|---|---|
console.log(1) | setTimeout2 | process3 |
console.log(4) | setTimeout6 | then5 |
console.log(7) | setTimeout8 |
- 主流程輸出:1, 4, 7
- 執行第一個 Micro event queue:輸出 3
- 第二個 Micro event queue:輸出 5
- Micro event queue 清空,第一輪執行完畢
第二輪事件迴圈
主流程 | Macro event queue | Micro event queue |
---|---|---|
setTimeout2 | setTimeout6 | |
setTimeout8 |
- 主流程輸出 2
- Micro event queue 為空,第二輪執行完畢
第三輪事件迴圈
主流程 | Macro event queue | Micro event queue |
---|---|---|
setTimeout6 | setTimeout8 | |
- 主流程輸出 6
- 第三輪執行完畢
第四輪事件迴圈
主流程 | Macro event queue | Micro event queue |
---|---|---|
setTimeout9 | setTimeout10 | then9 |
- 注意,這裡執行輸出 8 後,resolve,這時才向 Micro event queue 壓入 then 回撥
- 執行 then9 回撥,輸出 9
- 又有新的 setTimeout,壓入 Macro event queue
- 這輪迴圈沒有東西可執行,結束
第五輪事件迴圈
主流程 | Macro event queue | Micro event queue |
---|---|---|
console.log(10) | then12 | |
console.log(11) |
- 第五輪,setTimeout10 進入主流程,輸出 10
- 遇到 promise,輸出 11
- resolve, 壓入 then 到 Micro event queue
- 取出 Micro event queue 執行,輸出 12
習題3
// 以下程式碼在 Node 環境執行:process.nextTick 由 Node 提供
console.log("1")
setTimeout(function () {
console.log("2")
process.nextTick(function () {
console.log("3")
})
new Promise(function (resolve) {
console.log("4")
resolve()
}).then(function () {
console.log("5")
})
})
process.nextTick(function () {
console.log("6")
})
new Promise(function (resolve) {
console.log("7")
resolve()
}).then(function () {
console.log("8")
})
setTimeout(function () {
console.log("9")
process.nextTick(function () {
console.log("10")
})
new Promise(function (resolve) {
console.log("11")
resolve()
}).then(function () {
console.log("12")
})
})
// 最終輸出:1 7 6 8 2 4 3 5 9 11 10 12
習題4
setTimeout(()=>{
console.log("setTimeout1");
Promise.resolve().then(data => {
console.log(222);
});
},0);
setTimeout(()=>{
console.log("setTimeout2");
},0);
Promise.resolve().then(data=>{
console.log(111);
});
//111 setTimeout1 222 setTimeout2
習題4解析
- 主執行緒上沒有需要執行的程式碼
- 接著遇到setTimeout 0,它的作用是在 0ms 後將回撥函式放到宏任務佇列中(這個任務在下一次的事件迴圈中執行)。
- 接著遇到setTimeout 0,它的作用是在 0ms 後將回撥函式放到宏任務佇列中(這個任務在再下一次的事件迴圈中執行)。
- 首先檢查微任務佇列, 即 microtask佇列,發現此佇列不為空,執行第一個promise的then回撥,輸出 '111'。
- 此時microtask佇列為空,進入下一個事件迴圈, 檢查宏任務佇列,發現有 setTimeout的回撥函式,立即執行回撥函式輸出 'setTimeout1',檢查microtask
佇列,發現佇列不為空,執行promise的then回撥,輸出'222',microtask佇列為空,進入下一個事件迴圈。 - 檢查宏任務佇列,發現有 setTimeout的回撥函式, 立即執行回撥函式輸出'setTimeout2'。
習題5
console.log('script start');
setTimeout(function () {
console.log('setTimeout---0');
}, 0);
setTimeout(function () {
console.log('setTimeout---200');
setTimeout(function () {
console.log('inner-setTimeout---0');
});
Promise.resolve().then(function () {
console.log('promise5');
});
}, 200);
Promise.resolve().then(function () {
console.log('promise1');
}).then(function () {
console.log('promise2');
});
Promise.resolve().then(function () {
console.log('promise3');
});
console.log('script end');
/*script startscript endpromise1promise3promise2setTimeout---0setTimeout---200promise5inner-setTimeout---0*/
習題5解析
- 首先順序執行完主程式上的同步任務,第一句和最後一句的console.log
- 接著遇到setTimeout 0,它的作用是在 0ms 後將回撥函式放到宏任務佇列中(這個任務在下一次的事件迴圈中執行)。
- 接著遇到setTimeout 200,它的作用是在 200ms 後將回撥函式放到宏任務佇列中(這個任務在再下一次的事件迴圈中執行)。
- 同步任務執行完之後,首先檢查微任務佇列, 即 microtask佇列,發現此佇列不為空,執行第一個promise的then回撥,輸出 'promise1',然後執行第二個promise的then回撥,輸出'
promise3',由於第一個promise的.then()的返回依然是promise,所以第二個.then()會放到microtask佇列繼續執行,輸出 'promise2'; - 此時microtask佇列為空,進入下一個事件迴圈, 檢查宏任務佇列,發現有 setTimeout的回撥函式,立即執行回撥函式輸出 'setTimeout---0',檢查microtask 佇列,佇列為空,進入下一次事件迴圈.
- 檢查宏任務佇列,發現有 setTimeout的回撥函式, 立即執行回撥函式輸出'setTimeout---200'.
- 接著遇到setTimeout 0,它的作用是在 0ms 後將回撥函式放到宏任務佇列中,檢查微任務佇列,即 microtask 佇列,發現此佇列不為空,執行promise的then回撥,輸出'promise5'。
- 此時microtask佇列為空,進入下一個事件迴圈,檢查宏任務佇列,發現有 setTimeout 的回撥函式,立即執行回撥函式輸出,輸出'inner-setTimeout---0'。程式碼執行結束.
習題6
console.log("1");
setTimeout(function cb1(){
console.log("2")
}, 0);
new Promise(function(resolve, reject) {
console.log("3")
resolve();
}).then(function cb2(){
console.log("4");
})
console.log("5")
// 1 3 5 4 2
習題6解析
習題7
console.log("1");
setTimeout(() => {
console.log("2")
new Promise(resolve => {
resolve()
}).then(() => {
console.log("3")
})
}, 0);
setTimeout(() => {
console.log("4")
}, 0);
console.log("5")
// 1 5 2 3 4
習題7解析
習題8
console.log("1");
setTimeout(() => {
console.log("2")
new Promise(resolve => {
console.log(6)
resolve()
}).then(() => {
console.log("3")
})
}, 0);
setTimeout(() => {
console.log("4")
}, 0);
console.log("5")
// 1 5 2 6 3 4
習題8解析
習題9
console.log('start')
setTimeout(function(){
console.log('宏任務1號')
})
Promise.resolve().then(function(){
console.log('微任務0號')
})
console.log('執行棧執行中')
setTimeout(function(){
console.log('宏任務2號')
Promise.resolve().then(function(){
console.log('微任務1號')
})
},500)
setTimeout(function(){
console.log('宏任務3號')
setTimeout(function(){
console.log('宏任務4號')
Promise.resolve().then(function(){
console.log('微任務2號')
})
},500)
Promise.resolve().then(function(){
console.log('微任務3號')
})
},600)
console.log('end')
// start 執行棧執行中 end 微任務0號 宏任務1號 宏任務2號 微任務1號 宏任務3號 微任務3號 宏任務4號 微任務2號
習題9解析
習題10
function test() {
console.log(1)
setTimeout(function () { // timer1
console.log(2)
}, 1000)
}
test();
setTimeout(function () { // timer2
console.log(3)
})
new Promise(function (resolve) {
console.log(4)
setTimeout(function () { // timer3
console.log(5)
}, 100)
resolve()
}).then(function () {
setTimeout(function () { // timer4
console.log(6)
}, 0)
console.log(7)
})
console.log(8)
//1 4 8 7 3 6 5 2
習題10解析
結合我們上述的JS執行機制再來看這道題就簡單明瞭的多了
- JS是順序從上而下執行
- 執行到test(),test方法為同步,直接執行,console.log(1)列印1
- test方法中setTimeout為非同步宏任務,回撥我們把它記做timer1放入宏任務佇列
- 接著執行,test方法下面有一個setTimeout為非同步宏任務,回撥我們把它記做timer2放入宏任務佇列
- 接著執行promise,new Promise是同步任務,直接執行,列印4
- new Promise裡面的setTimeout是非同步宏任務,回撥我們記做timer3放到宏任務佇列
- Promise.then是微任務,放到微任務佇列
- console.log(8)是同步任務,直接執行,列印8
- 主執行緒任務執行完畢,檢查微任務佇列中有Promise.then
- 開始執行微任務,發現有setTimeout是非同步宏任務,記做timer4放到宏任務佇列
- 微任務佇列中的console.log(7)是同步任務,直接執行,列印7
- 微任務執行完畢,第一次迴圈結束
- 檢查宏任務佇列,裡面有timer1、timer2、timer3、timer4,四個定時器宏任務,按照定時器延遲時間得到可以執行的順序,即Event
Queue:timer2、timer4、timer3、timer1,依次拿出放入執行棧末尾執行 (插播一條:瀏覽器 event loop 的 Macrotask queue,就是宏任務佇列在每次迴圈中只會讀取一個任務) - 執行timer2,console.log(3)為同步任務,直接執行,列印3
- 檢查沒有微任務,第二次Event Loop結束
- 執行timer4,console.log(6)為同步任務,直接執行,列印6
- 檢查沒有微任務,第三次Event Loop結束
- 執行timer3,console.log(5)同步任務,直接執行,列印5
- 檢查沒有微任務,第四次Event Loop結束
- 執行timer1,console.log(2)同步任務,直接執行,列印2
- 檢查沒有微任務,也沒有宏任務,第五次Event Loop結束 結果:1,4,8,7,3,6,5,2
習題11
setTimeout(() => {
console.log(1)
}, 0)
new Promise((resolve, reject) => {
console.log(2)
resolve(3)
}).then(val => {
console.log(val)
})
console.log(4)
// 2 4 3 1
習題11解析
習題12
for (let i = 0; i < 5; i++) {
console.log(i)
}
console.log('done')
// 0 1 2 3 4 done
習題12解析
習題13
console.log(1)
Promise.resolve().then(() => {
console.log(2)
})
console.log(3)
//1 3 2
習題13解析
習題14
setTimeout(() => {
console.log(1)
}, 0)
for (let i = 2; i <= 3; i++) {
console.log(i)
}
console.log(4)
setTimeout(() => {
console.log(5)
}, 0)
for (let i = 6; i <= 7; i++) {
console.log(i)
}
console.log(8)
//2 3 4 6 7 8 1 5
習題14解析
習題15
console.log(1)
async function async1() {
await async2()
console.log(2)
}
async function async2() {
console.log(3)
}
async1()
setTimeout(() => {
console.log(4)
}, 0)
new Promise(resolve => {
console.log(5)
resolve()
})
.then(() => {
console.log(6)
})
.then(() => {
console.log(7)
})
console.log(8)
// 1 3 5 8 2 6 7 4
習題15解析
習題16
console.log(1)
function a() {
return new Promise(resolve => {
console.log(2)
setTimeout(() => {
console.log(3)
}, 0)
resolve()
})
}
a().then(() => {
console.log(4)
})
//1 2 4 3
習題16解析
習題17
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
//script start、script end、promise1、promise2、setTimeout
習題17解析
- 整體script作為第一個宏任務進入主執行緒,遇到console.log,輸出script start
- 遇到 setTimeout,其回撥函式被分發到宏任務 Event Queue 中
- 遇到 Promise,其 then函式被分到到微任務 Event Queue 中,記為 then1,之後又遇到了 then 函式,將其分到微任務 Event Queue 中,記為 then2
遇到 console.log,輸出 script end。至此,Event Queue中存在三個任務,如下表:
主流程 Macro event queue Micro event queue console.log('script start') console.log('setTimeout') console.log('promise1') console.log('script end') console.log('promise1')
習題18
console.log('script start');
setTimeout(function() {
console.log('timeout1');
}, 10);
new Promise(resolve => {
console.log('promise1');
resolve();
setTimeout(() => console.log('timeout2'), 10);
}).then(function() {
console.log('then1')
})
console.log('script end');
//script start、promise1、script end、then1、timeout1、timeout2
習題18解析
首先,事件迴圈從宏任務 (macrotask) 佇列開始,最初始,宏任務佇列中,只有一個 script(整體程式碼)任務;當遇到任務源 (task source)
時,則會先分發任務到對應的任務佇列中去。所以,就和上面例子類似,首先遇到了console.log,輸出 script start; 接著往下走,遇到 setTimeout 任務源,將其分發到任務佇列中去,記為 timeout1; 接著遇到
promise,new promise 中的程式碼立即執行,輸出 promise1, 然後執行 resolve ,遇到 setTimeout ,將其分發到任務佇列中去,記為 timemout2, 將其 then 分發到微任務佇列中去,記為
then1; 接著遇到 console.log 程式碼,直接輸出 script end 接著檢查微任務佇列,發現有個 then1 微任務,執行,輸出then1 再檢查微任務佇列,發現已經清空,則開始檢查宏任務佇列,執行 timeout1,輸出
timeout1; 接著執行 timeout2,輸出 timeout2 至此,所有的都佇列都已清空,執行完畢。其輸出的順序依次是:script start, promise1, script end, then1, timeout1,
timeout2。
有個小 tip:從規範來看,microtask 優先於 task 執行,所以如果有需要優先執行的邏輯,放入microtask 佇列會比 task 更早的被執行。 最後的最後,記住,JavaScript
是一門單執行緒語言,非同步操作都是放到事件迴圈佇列裡面,等待主執行棧來執行的,並沒有專門的非同步執行執行緒。
習題19
console.log(1)
setTimeout(function() {
console.log(2)
},0)
setTimeout(function() {
console.log(3)
},0)
console.log(4)
// 1 4 2 3
習題19解析
習題20
function fun1(){
console.log(1)
}
function fun2(){
console.log(2)
fun1()
console.log(3)
}
fun2()
// 2 1 3
習題20解析
習題21
function func1(){
console.log(1)
}
function func2(){
setTimeout(()=>{
console.log(2)
},0)
func1()
console.log(3)
}
func2()
// 1 3 2
習題21解析
習題22
var p = new Promise(resolve=>{
console.log(4) //這裡沒有執行p也要有輸出 所以4是最開始的
resolve(5)
})
function func1(){
console.log(1)
}
function func2(){
setTimeout(()=>{
console.log(2)
},0)
func1()
console.log(3)
p.then(resolve=>{
console.log(resolve)
})
}
func2()
//4 1 3 5 2
習題22解析
習題21
console.log('start')
const interval = setInterval(() => {
console.log('setInterval')
}, 0)
setTimeout(() => {
console.log('setTimeout 1')
Promise.resolve()
.then(() => {
console.log('promise 1')
})
.then(() => {
console.log('promise 2')
})
.then(() => {
setTimeout(() => {
console.log('setTimeout 2')
Promise.resolve()
.then(() => {
console.log('promise 3')
})
.then(() => {
console.log('promise 4')
})
.then(() => {
clearInterval(interval)
})
}, 0)
})
console.log('time end')
}, 0)
Promise.resolve().then(() => {
console.log('promise 5')
}).then(() => {
console.log('promise 6')
})
// start
// promise 5
// promise 6
// setInterval
// setTimeout 1
// time end
// promise 1
// promise 2
// setInterval
// setTimeout 2
// setInterval
// promise 3
// promise 4
習題22解析
解析: (1)先按照 macrotask 和 microtask 劃分程式碼:
console.log('start')
setInterval 是 macrotask,其回撥函式在 microtask 後執行
const interval = setInterval(() => {
console.log('setInterval')
}, 0)
setTimeout 是 macrotask,其回撥函式放在下一車(cycle 2)執行
setTimeout(() => ... , 0)
Promise.resolve () 的兩個 then () 是 microtask
Promise.resolve()
//microtask
.then(() => {
console.log('promise 5')
})
//microtask
.then(() => {
console.log('promise 6')
})
(2)第一班車(cycle 1):
進棧
第一個 macrotask 是 setInterval,回撥函式放下一車(cycle 2)的開頭執行, 第二個 macrotask 是 setTimeout,回撥函式放下下一車(cycle 3)的開頭執行,
清空棧, 輸出:start 執行 microtasks,直至清空該佇列,即 Promise.resolve () 的兩個 then (), 輸出:promise 5 promise 6
(3)第二班車(cycle 2): 執行 setInterval 的回撥, 輸出:setInterval, 同時下一個 setInterval 也是 macrotask 但要放到 下下下一車(cycle 4)執行回撥,即
下下一車(cycle 3)setTimeout 的後面
此時 setInterval 中沒有 microtasks,所以該佇列是空的,故進行下一車(cycle 3)
(4)第三班車(cycle 3) 執行 setTimeout 的回撥, 輸出 setTimeout 1 執行 microtasks,直至清空該佇列,即 Promise.resolve () 的第一個和第二個 then (),
輸出:promise 1 promise 2
而 第三個 then () 中的 setTimeout 是 macrotask ,放到下下下下一車(cycle 5)執行回撥, 第四個 then () 是緊跟著第三個 then () 的,所以在 下下下下一車(cycle 5)執行
此時 microtasks 已空,故進行下一車(cycle 4)
(5)第四班車(cycle 4) 由(3)得,執行 setInterval , 輸出:setInterval
此時 setInterval 中沒有 microtasks,所以該佇列是空的,故進行下一車(cycle 5)
同時下一個 setInterval 也是 macrotask 但要放到 下下下下下一車(cycle 6)執行回撥,
(6)第五班車('cycle 5') 由(4)得,執行 setTimeout 輸出:setTimeout 2
執行 microtasks,直至清空該佇列,即 Promise.resolve () 的第一個和第二個 then (),
輸出:promise 3 promise 4
接著執行第三個 then () --> clearInterval(interval),將下下下下下一車(cycle 6)要執行回撥的 setInterval 清除
此時 microtasks 已空, 同時整段程式碼執行完畢。