setTimeout and setInterval in async JavaScript

非同步 JavaScript 時間操縱之術: setTimeout 與 setInterval

學會非同步操縱時間

在介紹到同步與非同步代碼時,時常會以 setTimeout 以及 setInterval 來模擬程式被非同步的執行,本文將會詳細講解其背後原理與實際上如何操作。

setTimeout

如果想要在一段時間後執行一段代碼會使用 setTimeout🔗 ,它是存在於 DOM API🔗 中的方法,可達成:「在 x 毫秒後,執行 y 函式」的功能。

setTimeout(回呼函式, 等待毫秒數);
// 寫法一:使用具名函式撰寫
function callback() {
// ...
}
setTimeout(callback, 1000);
// 寫法二:使用匿名函式撰寫
setTimeout(() => {
// ...
}, 1000);

拿一些更為實際的案例來展示:小明得知晚餐煮好了,但他要打電腦,所以和家人說再一分鐘後就離開電腦去吃晚餐。

// Input 輸入
// 告知家人再一分鐘
console.log('知道了,再等一分鐘就過去');
// 倒數一分鐘後去小明去吃晚餐
setTimeout(() => {
console.log('小明去吃晚餐了');
}, 60 * 1000);
// Output 輸出
'知道了,再等一分鐘就過去'(經過一分鐘後);
('小明去吃晚餐了');

setInterval

setTimeout 只會執行一次,而 setInterval 則是會在輸入的間隔時間內不斷重複執行輸入的函式,它們有著一模一樣的語法:

setInterval(回呼函式, 等待毫秒數);
// Input 輸入
let timePassed = 0
setInterval(() => {
timePassed += 1
console.log(timePassed)
}, 1000)
// Output 輸出
1
2
3
...

取消倒數

要清除時間倒數,可以使用 clearInterval(timerId)clearTimeout(timerId)。每當使用以上方法時,就會拿到一筆「標識符 ID」,放入 clearTimeoutclearInterval 函式中即可終止整個計畫調用。

const timerId = setTimeout(...);
clearTimeout(timerId);
const timerId = setInterval(...);
clearInterval(timerId);

背後理論

一切都是如此的自然,但仔細想想似乎有些不合理的事情?

瀏覽器中 JavaScript 是以單一執行緒來同步執行程式的,白話來說就是一次只能執行一件事,那這樣要如何才能辦到同時間「等待」又「執行後續程式」的?

實際上,JavaScript 確實有執行 setTimeout ,只是提交了「等待」這件事給 Web API 處理,詳細可以在文章底部有我寫的另一篇文章:從動圖輕鬆入門非同步 JavaScript 可參考。

零延遲 Zero Delay

當把 setTimeout 的延遲設置為 0 秒時,並不意味著回呼函式中的內容會立即就被執行,實際上要考慮到 Call Stack 中還有沒有事要完成,該參數僅代表「要求執行環境處理所需的最少等待時間,而非一個保證時間。」

所以要做精確時間的等待最好不要使用 setTimeout

// Input 輸入
setTimeout(() => {
console.log('bar');
}, 0);
console.log('foo');
// Output 輸出
('foo');
('bar');

參考資料