javascript:为什么Promises比setTimeout()更快?

·  阅读 1570
javascript:为什么Promises比setTimeout()更快?

1.实验

我们来做个实验。什么执行得更快:立即解决的承诺或立即超时(又称超时0 millisecond)?

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)是一个静态函数,它返回一个立即解决的承诺。setTimeout(callback, 0) ,执行回调,延迟时间为0 millisecond。

打开演示并检查控制台。你会注意到'Resolved!' 先被记录下来,然后是'Timeout completed!' 。一个立即解决的承诺比一个立即超时的承诺处理得更快。

可能是因为Promise.resolve(true).then(...) 是在setTimeout(..., 0) 之前调用的,所以承诺的处理速度更快? 这个问题很合理。

让我们稍微改变一下实验的条件,先调用setTimeout(..., 0)

setTimeout(function timeout() {
  console.log('Timed out!');
}, 0);
Promise.resolve(1).then(function resolve() {
  console.log('Resolved!');
});
// logs 'Resolved!'
// logs 'Timed out!'
复制代码

打开演示,看看控制台。嗯......同样的结果!

实验表明,一个立即解决的承诺会在立即超时之前被处理。最大的问题是...为什么?

2.事件循环

与异步JavaScript有关的问题可以通过研究事件循环来回答。让我们回顾一下异步JavaScript工作方式的主要组成部分。

注意:如果你对事件循环不熟悉,我建议在进一步阅读之前先看这个视频

Event Loop Empty

调用堆栈是一个LIFO(Last In, First Out)结构,用于存储代码执行过程中创建的执行环境。简单地说,调用堆栈执行函数。

网络API是异步操作(获取请求、承诺、定时器)及其回调的地方,等待完成。

任务队列(也叫宏任务)是一个FIFO(先进先出)结构,它保存着准备执行的异步操作的回调。例如,一个超时的setTimeout() 的回调--准备被执行--被排在任务队列中。

任务队列(也被称为微任务)是一个FIFO(先进先出)结构,保存着准备执行的承诺的回调。例如,一个已履行的承诺的解析或拒绝回调被排在作业队列中。

最后,事件循环永久地监视调用栈是否为空。如果调用栈是空的,事件循环会查看作业队列或任务队列,并将任何准备执行的回调排入调用栈。

3.工作队列与任务队列

让我们再从事件循环的角度看一下这个实验。我将对代码的执行做一个逐步的分析。

A) 调用堆栈执行setTimeout(..., 0) ,并安排一个定时器。timeout() 回调被存储在Web APIs中。

setTimeout(function timeout() {
  console.log('Timed out!');
}, 0);
Promise.resolve(1).then(function resolve() {
  console.log('Resolved!');
});
复制代码

Event Loop

B) 调用堆栈执行Promise.resolve(true).then(resolve) ,并安排了一个承诺解析。resolved() 回调存储在Web APIs中。

setTimeout(function timeout() {
  console.log('Timed out!');
}, 0);
Promise.resolve(1).then(function resolve() {
  console.log('Resolved!');
});
复制代码

Event Loop

C)承诺立即被解决,同时计时器也立即超时。因此,定时器回调timeout()排到任务队列中,承诺回调resolve()排到工作队列中。

Event Loop

D) 现在是有趣的部分:事件循环优先于任务去排队工作。事件循环从作业队列中取消承诺回调resolve() ,并把它放到调用栈中。然后调用堆栈执行承诺回调resolve()

setTimeout(function timeout() {
  console.log('Timed out!');
}, 0);
Promise.resolve(1).then(function resolve() {
  console.log('Resolved!');
});
复制代码

Event Loop

E) 最后,事件循环从任务队列中的定时器回调timeout() ,并放入调用堆栈。然后调用堆栈执行定时器回调timeout()

setTimeout(function timeout() {
  console.log('Timed out!');
}, 0);
Promise.resolve(1).then(function resolve() {
  console.log('Resolved!');
});
复制代码

'Timed out!' 被记录到控制台。

Event Loop

调用堆栈是空的。脚本的执行已经完成。

4.总结

为什么一个立即解决的承诺比一个立即的定时器处理得快?

因为事件循环优先从作业队列(存储已实现的承诺的回调)中去排队作业,而不是从任务队列(存储已超时的setTimeout() 回调)中去排队任务。

分类:
代码人生
标签:
收藏成功!
已添加到「」, 点击更改