天天看點

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

英文 | https://javascript.plainenglish.io/6-interview-questions-that-combine-promise-and-settimeout-34c430fc297e

翻譯 | 楊小愛

在我們開始之前,我希望你能理清幾個知識點。

事件循環按以下順序執行:

  1. JS引擎中有兩個任務隊列:macrotask queue和microtask queue
  2. 整個腳本最初作為宏任務執行
  3. 執行時直接執行同步代碼,宏任務進入宏任務隊列,微任務進入微任務隊列
  4. 目前宏任務完成後,檢查微任務隊列,依次執行所有微任務
  5. 執行浏覽器 UI 線程的渲染(您可以在本文中忽略它)
  6. 如果存在任何 Web Worker 任務,則執行它(您可以在本文中忽略這一點)
  7. 檢查宏任務隊列,如果不為空,則傳回步驟2,執行下一個宏任務。

值得注意的是第4步:當一個macrotask完成後,先依次執行其他所有microtask,然後再執行下一個macrotask。

Mircotasks 包括:MutationObserver、Promise.then() 和 Promise.catch(),其他基于 Promise 的技術如 fetch API、V8 垃圾收集過程、node 環境中的 process.nextTick()。

Marcotasks 包括:初始腳本、setTimeout、setInterval、setImmediate、I/O、UI 渲染。

好吧,如果你不完全了解這裡發生了什麼,讓我們用例子來練習。

一共有 10 道題:前 4 道是簡單的 Promise 題,幫助你了解微任務;後面 6 個問題是 Promise 和 setTimeout 的混合。

1、

讓我們從一個簡單的例子開始來解釋微任務。

例子:

const promise1 = new Promise((resolve, reject) => {
  console.log(1);
  resolve('success')
});
promise1.then(() => {
  console.log(3);
});
console.log(4);      

分析:

首先,執行此代碼的前四行,控制台會列印出1,然後promise1就會變成resolved狀态。

然後,開始執行 promise1.then(() => {console.log(3);}); 片段。因為 promise1 現在處于已解決狀态,是以 () => {console.log(3);} 将立即添加到微任務隊列中。

但是,我們知道 () => {console.log(3);} 是一個微任務,是以它不會立即被調用。

然後,執行最後一行代碼(console.log(4);),并在控制台列印 4。

至此,所有同步的代碼,即目前的宏任務,都被執行了。然後 JavaScript 引擎檢查微任務隊列并依次執行它們。

() => {console.log(3);} 然後執行并在控制台中列印 4。

結果如下:

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

2、

例子

const promise1 = new Promise((resolve, reject) => {
  console.log(1);
});
promise1.then(() => {
  console.log(3);
});
console.log(4);      

分析:

這個例子和上一個非常相似,隻是在這個例子中,promise1 會一直處于挂起狀态,是以 () => {console.log(3);} 不會被執行,控制台也不會輸出3。

結果:

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

3、

例子

const promise1 = new Promise((resolve, reject) => {
  console.log(1)
  resolve('resolve1')
})
const promise2 = promise1.then(res => {
  console.log(res)
})
console.log('promise1:', promise1);
console.log('promise2:', promise2);      

仔細考慮控制台列印結果的順序和每個 Promise 的狀态。

分析:

  • 首先,前四行代碼和之前一樣,在控制台列印1,promise1的狀态是resolved。
  • 然後,執行 const promise2 = promise1.then(...),res => {console.log(res)} 被添加到微任務隊列中。同時,promise1.then() 将傳回一個新的待處理的 promise 對象。
  • 然後,執行console.log('promise1:', promise1); ,控制台列印出字元串'promise1'和處于已解決狀态的promise1。
  • 然後,執行console.log('promise2:', promise2); ,控制台列印出字元串‘promise2’和處于挂起狀态的promise2。
  • 至此,所有同步的代碼,即目前的宏任務,都被執行了。然後 JavaScript 引擎檢查微任務隊列并依次執行它們。
  • res => {console.log(res)} 是微任務隊列中唯一的任務,現在将被執行。然後控制台将列印 'reslove1' 。

結果:

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

4、

例子

const fn = () => (new Promise((resolve, reject) => {
  console.log(1)
  resolve('success')
}));
fn().then(res => {
  console.log(res)
});
console.log(2)      

分析:

與之前不同的是,在這個例子中,建立 Promise 對象的行為發生在 fn 函數中。fn函數雖然是一個普通的同步函數,但并沒有什麼特别之處,這個例子還是很簡單的。

結果:

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

前面的例子比較簡單,現在問題會逐漸變得複雜,你準備好了嗎?

5、

例子:

console.log('start')
setTimeout(() => {
  console.log('setTimeout')
})
Promise.resolve().then(() => {
  console.log('resolve')
})
console.log('end')      

分析:

首先,JS引擎中有兩個任務隊列:宏任務隊列和微任務隊列。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

在程式開始時,所有的初始代碼都被視為一個宏任務,被推入宏任務隊列。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

然後,執行第一行代碼 console.log('start') 并在控制台中列印'start'。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

那麼 ,setTimeout(...) 就是一個等待時間為 0 的定時器,會立即執行。正如我們在本文開頭提到的,setTimeout 是一個宏任務,是以 setTimeout(...), () => {console.log('setTimeout')} 的回調函數,不會立即執行,它會 被壓入宏任務隊列,等待稍後執行。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

然後,它開始執行 Promise.resolve().then(...),并且 () => {console.log('resolve')} 被推入微任務隊列。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

現在,執行console.log(‘end’),在控制台列印‘end’,第一個宏任務就完成了。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

當一個宏任務完成後,JS引擎會先檢查微任務的隊列,然後,依次執行所有的微任務。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

當微任務隊列為空時,JS引擎檢查宏任務隊列并開始執行下一個宏任務。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

值得強調的是,雖然 setTimeout(...) 比 Promise.resolve().then(...) 執行得更早,但 setTimeout(...) 的回調函數仍然執行得較晚,因為 setTimeout 是一個宏任務。這是新手犯錯誤最多的地方。

好的,這就是上面示例代碼的運作方式。我希望我的草圖能幫到你。

結果:

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

6、

例子

const promise = new Promise((resolve, reject) => {
  console.log(1);
  setTimeout(() => {
    console.log("timerStart");
    resolve("success");
    console.log("timerEnd");
  }, 0);
  console.log(2);
});
promise.then((res) => {
  console.log(res);
});
console.log(4);      

分析:

首先,我們暫時忽略那些回調函數,簡化代碼:

const promise = new Promise((resolve, reject) => {
  console.log(1);
  setTimeout(..., 0);
  console.log(2);
});
promise.then(...);
console.log(4);      

然後我們像以前一樣繪制圖檔。起初,所有的代碼都可以被認為是一個宏任務。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

然後開始執行new Promise(...),然後進入executor内部,執行console.log(1)。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

然後開始執行 setTimeout(..., 0) 。定時器立即結束,其回調函數被推入宏任務隊列。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

然後開始執行 console.log(2) 。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

現在開始執行 promise.then(...)。因為promise對象還處于pending狀态,是以它的回調函數還沒有壓入微任務隊列。也就是說,微任務隊列目前仍然是空的。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

然後開始執行 console.log(4) 。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

至此,第一個宏任務結束,微任務隊列還是空的,是以JS引擎開始下一個宏任務。

然後,開始執行 console.log('timerStart') 。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

現在 resolve() 函數被執行,promise 的狀态将被解析,promise.then(…) 的回調函數被推入微任務隊列。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

然後,開始執行 console.log('timerEnd') 。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

現在目前的宏任務已經結束,JS引擎再次檢查微任務隊列,依次執行。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

結果:

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

7、

例子:

const timer1 = setTimeout(() => {
  console.log('timer1');
const timer3 = setTimeout(() => { 
    console.log('timer3')
  }, 0)
}, 0)
const timer2 = setTimeout(() => {
  console.log('timer2')
}, 0)
console.log('start')      

分析:

本例中有 3 個 setTimeout 函數,是以程式累加了 3 個額外的宏任務。

首先,讓我們繪制初始宏任務隊列。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

然後,開始執行 timer1 對應的 setTimeout(...) 。同時,建立了一個新的宏任務。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

然後,開始執行 timer2 對應的 setTimeout 。同時,另一個新的宏任務被建立。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

好的,現在我們有了三個宏任務,沒有微任務。

然後

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

現在第一個宏任務和它的執行都完成了,而微任務隊列仍然是空的,JS引擎将開始執行下一個宏任務。

console.log('timer1') 被執行。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

然後,開始執行 timer3 對應的 setTimeout(...) 。建立了一個新的宏任務。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

然後

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

然後

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

結果

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

8、

例子

const timer1 = setTimeout(() => {
  console.log('timer1');
  const promise1 = Promise.resolve().then(() => {
    console.log('promise1')
  })
}, 0)
const timer2 = setTimeout(() => {
  console.log('timer2')
}, 0)
console.log('start')      

分析:

此示例與上一個示例類似,不同之處在于我們将其中一個 setTimeout 替換為 Promise.then。因為 setTimeout 是宏任務而 Promise.then 是微任務,并且微任務優先于宏任務,是以控制台輸出的順序是不一樣的。

首先,讓我們繪制初始任務隊列。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

然後

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

然後

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

然後

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

注意此時 Promise.then() 正在建立一個微任務。它的回調函數在下一個宏任務之前由 JS 引擎執行。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

然後

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

注意,此時Promise.then()正在建立一個微任務。它的回調函數在下一個宏任務之前由 JS 引擎執行。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

然後結束。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

結果:

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

9、

例子

const promise1 = Promise.resolve().then(() => {
  console.log('promise1');
  const timer2 = setTimeout(() => {
    console.log('timer2')
  }, 0)
});
const timer1 = setTimeout(() => {
  console.log('timer1')
  const promise2 = Promise.resolve().then(() => {
    console.log('promise2')
  })
}, 0)
console.log('start');      

分析:

在這個例子中,宏任務和微任務交替建立,這是一個困難的話題。如果你隻是在頭腦中思考,那麼,很容易犯錯誤。但是如果你開始和我一起畫圖,很容易找到正确的答案。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

首先,讓我們繪制初始宏任務隊列。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

然後執行第一段代碼,并建立一個微任務。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

然後執行第二段代碼,并建立一個宏任務

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

然後

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

目前宏任務完成,微任務隊列中的任務開始。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

然後,開始執行setTimeout(...)與 timer2 相關的并建立一個新的宏任務

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

目前的微任務隊列被清空,開始下一個宏任務。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

然後,建立另一個微任務。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

目前宏任務已完成,JS引擎再次檢查微任務隊列,發現隊列不為空,開始對微任務隊列中的任務進行優先級排序。

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

最後

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

結果

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

10、

例子

const promise1 = new Promise((resolve, reject) => {
  const timer1 = setTimeout(() => {
    resolve('success')
  }, 1000)
})
const promise2 = promise1.then(() => {
  throw new Error('error!!!')
})


console.log('promise1', promise1)
console.log('promise2', promise2)


const timer2 = setTimeout(() => {
  console.log('promise1', promise1);
  console.log('promise2', promise2);
}, 2000)      

分析:

  • 首先,它通過new Promise(…) 建立了promise1,它處于pending 狀态。還建立了一個延遲為 1 秒的計時器。
  • 然後,執行 const promise2 = promise1.then(...),因為 promise1 目前處于 Pending 狀态,是以 promise1.then() 的回調函數還不會加入到微任務隊列中。
  • 然後,執行 console.log('promise1', promise1) 。此時,promise1 仍處于 Pending 狀态。
  • 然後,執行 console.log('promise2', promise2) 。此時,promise2 仍處于 Pending 狀态。
  • 然後,執行 const timer2 = setTimeout(…) 。還建立了一個延遲為 2 秒的計時器。
  • 1000 毫秒後,timer1 完成。然後執行 thenresolve('success'),promise1 被解決。
  • 調用 promise1.then(...) 的回調函數,并執行 throw new Error('error!!!')。抛出一個錯誤,promise2 被拒絕。
  • 又過了 1000 毫秒,timer2 完成。() => {console.log('promise1', promise1); console.log('promise2', promise2);} 被執行。

結果:

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

總結

以上就是我今天與你分享的10道關于 Promise 和 setTimeout知識的面試題,希望這些面試題對你有幫助,如果你覺得有用的話,請記得點贊我,關注我,并将它分享給你身邊的朋友,也許能夠幫助到他。

最後,感謝你的閱讀,祝程式設計愉快!

10 個關于 Promise 和 setTimeout 知識的面試題,通過圖解一次說透徹

繼續閱讀