我知道setTimeout()
内部的回调函数将等待,直到附加到它的计时器到期并被推入回调队列。另一方面,一个promise一旦被完成或拒绝,它的回调就会被推到一个微任务队列中,这个微任务队列具有更高的优先级。
我的问题是哪一次是更快:一个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秒延迟,现在承诺将总是首先出现,因为在微任务队列内调度的回调具有更高的优先级
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
总是首先记录它的结果,因为它肯定会在下一个事件周期中执行。
另一方面,承诺中的setTimeout
将有2个事件周期,直到控制台日志执行(一个用于承诺解析,另一个用于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还没有被解决。然后任务队列得到处理,因此首先记录'timeout'。在此之后,第二次setTimeout被处理,promise被解析,然后解析的结果被打印到控制台中。
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
然而在第二个例子中,一旦执行主程序的任务退出,promise就会被解决,所以微任务队列首先被处理并记录'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');
});
我强烈推荐阅读这篇文章,它帮助我深入了解任务队列和微任务队列是如何工作的。