Promise中的setTimeout与普通setTimeout的区别

3
我知道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');
});


1
更快?我猜根本没有任何实质性的区别。反正你还是要等一整秒钟。 - Pointy
4
围绕运行时如何安排异步事件的调度设计应用程序是一个非常糟糕的想法。您无法保证超时时间将特别接近1000毫秒。 - Pointy
2
在这两种情况下,宏任务必须先完成。使用Promise会导致宏任务结果排队一个微任务。但我不确定这有什么用处。 - VLAZ
2
如果您能描述一下您要解决的问题,那将会非常有帮助。 - Pointy
2
@VLAZ是正确的。在第一段代码中,将会有两个setTimeout()调用非常快速地连续发生。运行时会将它们排队到计时器队列中。首先请求的那个将会首先触发,而第二个请求的将会在之后触发。第二个setTimeout()调用在Promise回调函数中并没有任何区别。 - Pointy
显示剩余10条评论
2个回答

1

setTimeout的实现方式是在最小延迟后执行,并且在浏览器线程空闲时才执行。因此,例如,如果您为延迟参数指定了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 则需要两个事件循环才能执行控制台日志(一个用于 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

0

关键要点是

每当一个任务退出时,事件循环会检查该任务是否正在将控制权返回给其他 JavaScript 代码。如果不是,则运行微任务队列中的所有微任务。

在您下面的第一个示例中,一旦执行主程序的任务退出,就没有任何微任务可以处理,因为承诺还没有由第二个 setTimeout 解决。 然后,任务队列被处理,所以“timeout”首先被记录。之后,处理第二个 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

而在您的第二个例子中,一旦执行主程序的任务退出,承诺就被解决了,因此微任务队列首先被处理并记录“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');
});

我强烈推荐阅读这篇文章,它帮助我深入理解任务队列和微任务队列的工作原理。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接