我知道
setTimeout()
中的回调函数将等到附加到它的计时器到期并将其推入回调队列。另一方面,一个承诺,一旦完成或被拒绝,它的回调就会被推送到一个具有更高优先级的微任务队列中。
我的问题是哪个更快:承诺中的
setTimeout
或简单的setTimeout
。如果前者不再被放入微任务队列,为什么setTimeout
单独先运行而不是相反?
setTimeout(() => {
console.log('timeout');
}, 1000);
let promise = new Promise(function(resolve, reject) {
// This is run automatically, let's run resolve after 1 second
setTimeout(() => resolve('promise!'), 1000);
});
promise.then(
(result) => console.log(result),
(err) => console.log(error)
);
// output: timeout -> promise || and not || promise -> timeout
现在假设我忘记了 1 秒的延迟,现在 promise 总是首先出现,因为在微任务队列中调度的回调具有更高的优先级
setTimeout(() => {
console.log('timeout');
}, 0);
let promise = new Promise(function(resolve, reject) {
// This is run automatically, let's run resolve after 1 second
// setTimeout(() => resolve('promise!'), 1000);
resolve('');
});
promise.then(() => {
console.log('promise');
});
setTimeout
的实现方式是在给定的最小延迟后执行,一旦浏览器的线程可以自由执行它。因此,举例来说,如果您为 delay 参数指定一个值 0,并且您认为它会“立即”执行,那么它不会。更准确地说,它将在下一个事件循环中运行(这是事件循环的一部分 - 负责执行代码的并发模型)。
我们以延迟值为0为例
setTimeout(() => {
console.log('timeout');
}, 0);
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve('promise!'), 0);
});
promise.then(
(result) => console.log(result),
(err) => console.log(error)
);
setTimeout
总是首先记录它的结果,因为它肯定会在下一个事件周期中执行。
另一方面,promise 中的
setTimeout
将有 2 个事件周期,直到控制台日志执行(一个用于 promise 解析,另一个用于 setTimeout 函数中的回调)。
请阅读延迟时间超过指定时间的原因 - https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#reasons_for_delays_longer_than_specified
关于 JS 事件循环的更多信息 - https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
关键要点是
每次任务退出时,事件循环检查任务是否将控制权返回给其他 JavaScript 代码。如果没有,它将运行微任务队列中的所有微任务。
在下面的第一个示例中,一旦执行主程序的任务退出,就没有任何微任务要处理,因为由于第二次 setTimeout,promise 尚未解决。然后处理任务队列,因此首先记录“超时”。之后,处理第二个 setTimeout 并解决承诺,然后在控制台打印解决结果。
setTimeout(() => {
console.log('timeout');
}, 1000);
let promise = new Promise(function(resolve, reject) {
// This is run automatically, let's run resolve after 1 second
setTimeout(() => resolve('promise!'), 1000);
});
promise.then(
(result) => console.log(result),
(err) => console.log(error)
);
// output: timeout -> promise || and not || promise -> timeout
而在您的第二个示例中,一旦执行主程序的任务退出,承诺就会得到解决,因此首先处理微任务队列并记录“承诺”。然后处理任务队列并记录“超时”。
setTimeout(() => {
console.log('timeout');
}, 0);
let promise = new Promise(function(resolve, reject) {
// This is run automatically, let's run resolve after 1 second
// setTimeout(() => resolve('promise!'), 1000);
resolve('');
});
promise.then(() => {
console.log('promise');
});
我强烈推荐阅读这篇文章,这有助于我深入了解任务队列和微任务队列的工作原理。