为何 Promise 比setTimeout() 更快?

实验

先作一个实验:来看看当即解决的 Promise 和当即超时( 0 毫秒的超时)哪一个执行的更快?javascript

Promise.resolve(1).then(function resolve() {
  console.log('Resolved!');
});

setTimeout(function timeout() {
  console.log('Timed out!');
}, 0);

// logs 'Resolved!'
// logs 'Timed out!'

Promise.resolve(1) 是一个静态函数,可返回当即解决的 promise。 setTimeout(callback, 0) 执行延迟为 0 毫秒的回调。前端

打开执行并检查控制台。您会看到日志先打印了 'Resolved!' ,而后打印了 'Timeout completed!'。当即解决的承诺比当即超时处理得更快。java

是由于 Promise.resolve(true).then(...)setTimeout(..., 0) 以前被调用了,因此 promise 的处理过程会更快吗?程序员

那咱们就修改一下实验条件,先调用 setTimeout(...,0)面试

setTimeout(function timeout() {
  console.log('Timed out!');
}, 0);

Promise.resolve(1).then(function resolve() {
  console.log('Resolved!');
});

// logs 'Resolved!'
// logs 'Timed out!'

执行并查看控制台,结果是同样的!segmentfault

尽管 setTimeout(..., 0)Promise.resolve(true).then(...) 以前被调用了,可是 'Resolved!' 仍然在 'Timed out!' 以前被输出。promise

实验代表,当即解决的 promise 在当即超时以前已获得处理。因此。。。这是为何?服务器

事件循环

与异步 JavaScript 有关的问题能够经过探索事件循环解答答。先回顾一下异步 JavaScript 的工做原理。微信

image.png

调用栈(call stack) 是 LIFO(后进先出)的结构,用于存储在代码执行期间建立的执行上下文。简而言之,调用栈执行用来函数。多线程

Web API 是异步操做(fetch 请求、promises、计时器),回调等待这里的工做完成。

任务队列(task queue)是一个 FIFO(先进先出)的结构,其中包含准备好执行的异步操做的回调。例如,超时的 setTimeout() 的回调(准备执行)进入任务队列中。

工做队列(job queue) 是一个 FIFO(先进先出)的结构,其中包含准备执行的 promise 的回调。例如,已解决的 resolve 或拒绝回调进入工做队列中。

最后,事件循环(event loop) 会一直监视调用栈是否为空。若是调用栈为空,则事件循环会查找工做队列或任务队列,并使准备好执行的回调出队到调用栈中。

工做队列与任务队列

下面从事件循环的角度来看一下前面的实验。我会逐步分析代码的执行状况。

  1. 调用堆栈执行 setTimeout(..., 0) 并“安排”一个计时器。 timeout() 回调存储在 Web API 中:
setTimeout(function timeout() {  console.log('Timed out!');}, 0);
Promise.resolve(1).then(function resolve() {
  console.log('Resolved!');
});

image.png

  1. 调用栈执行 Promise.resolve(true).then(resolve) 并“安排”一个 promise 解析。 resolved() 回调存储在 Web API 中:
setTimeout(function timeout() {
  console.log('Timed out!');
}, 0);

Promise.resolve(1).then(function resolve() {  console.log('Resolved!');});

image.png

  1. promise 当即解决,计时器当即超时。这时计时器回调 timeout() 被“排队”到任务队列,promise 回调 resolve() 被“排队”到工做队列:

image.png

  1.  这里是最有意思部分:事件循环优先级使工做排在任务之上。事件循环使 promise 回调 resolve() 从工做队列中出队,并将其放入调用栈中,而后调用栈执行 promise 回调 resolve()
setTimeout(function timeout() {
  console.log('Timed out!');
}, 0);

Promise.resolve(1).then(function resolve() {
  console.log('Resolved!');});

'Resolved!' 被输出到控制台。

image.png

  1. 最后,事件循环把计时器回调 timeout() 从任务队列中移出到调用栈中。而后调用栈执行计时器回调timeout()
setTimeout(function timeout() {
  console.log('Timed out!');}, 0);

Promise.resolve(1).then(function resolve() {
  console.log('Resolved!');
});

'Timed out!' 已输出到控制台。

image.png

此时调用栈为空。脚本执行已完成。

总结

为何当即解决的 promise 比当即计时器处理得更快?

是因为事件循环的“优先级”使任务队列(存储已实现的 promise 的回调)中的任务从任务队列(存储超时的setTimeout() 回调)中的任务中出队。

173382ede7319973.gif


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,天天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,天天都给你推送新鲜的前端技术文章


欢迎继续阅读本专栏其它高赞文章:


相关文章
相关标签/搜索