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 输出123...
取消倒数
要清除时间倒数,可以使用 clearInterval(timerId)
或 clearTimeout(timerId)
。每当使用以上方法时,就会获得一笔「标识符 ID」,放入 clearTimeout
或 clearInterval
函数中即可终止整个计划调用。
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');
参考资料
- YouTube - What the heck is the event loop anyway? | Philip Roberts | JSConf EU
- PJCHENder - [JS] 理解 JavaScript 中的事件循环、堆栈、队列和并发模式(Learn event loop, stack, queue, and concurrency mode of JavaScript in depth)
- MDN - 并行模型和事件循环