为什么 Promis 比setTimeout()更快?

空的事件循环

调用栈(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!');
});

事件循环
  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!');});

事件循环
  1. promise 立即解决,计时器立即超时。这时计时器回调 timeout()
    被“排队”到任务队列,promise 回调 resolve()
    被“排队”到工作队列:

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

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

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

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

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

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

Event Loop

此时调用栈为空。脚本执行已完成。
为什么立即解决的 promise 比立即计时器处理得更快?

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


强力推荐前端面试刷题神器

精彩文章回顾,点击直达