为什么 Promis 比setTimeout()更快?

开发 前端
先做一个实验:来看看立即解决的 Promis 和立即超时( 0 毫秒的超时)哪个执行的更快?

实验

先做一个实验:来看看立即解决的 Promis 和立即超时( 0 毫秒的超时)哪个执行的更快?

  1. Promise.resolve(1).then(function resolve() { 
  2.   console.log('Resolved!'); 
  3. }); 
  4.  
  5. setTimeout(function timeout() { 
  6.   console.log('Timed out!'); 
  7. }, 0); 
  8.  
  9. // logs 'Resolved!' 
  10. // logs 'Timed out!' 

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

[[375601]]

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

是因为 Promise.resolve(true).then(...) 在 setTimeout(..., 0) 之前被调用了,所以 promise 的处理过程会更快吗?

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

  1. setTimeout(function timeout() { 
  2.   console.log('Timed out!'); 
  3. }, 0); 
  4.  
  5. Promise.resolve(1).then(function resolve() { 
  6.   console.log('Resolved!'); 
  7. }); 
  8.  
  9. // logs 'Resolved!' 
  10. // logs 'Timed out!' 

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

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

实验表明,立即解决的 promise 在立即超时之前已得到处理。所以。。。这是为什么?

事件循环

与异步 JavaScript 有关的问题可以通过探索事件循环解答答。先回顾一下异步 JavaScript 的工作原理。

空的事件循环

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

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

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

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

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

工作队列与任务队列

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

(1) 调用堆栈执行 setTimeout(..., 0) 并“安排”一个计时器。timeout() 回调存储在 Web API 中:

  1. setTimeout(function timeout() {  console.log('Timed out!');}, 0); 
  2. Promise.resolve(1).then(function resolve() { 
  3.   console.log('Resolved!'); 
  4. }); 

事件循环

(2) 调用栈执行 Promise.resolve(true).then(resolve) 并“安排”一个 promise 解析。resolved() 回调存储在 Web API 中:

  1. setTimeout(function timeout() { 
  2.   console.log('Timed out!'); 
  3. }, 0); 
  4.  
  5. Promise.resolve(1).then(function resolve() {  console.log('Resolved!');}); 

事件循环

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

事件循环

(4) 这里是最有意思部分:事件循环优先级使工作排在任务之上。事件循环使 promise 回调 resolve() 从工作队列中出队,并将其放入调用栈中,然后调用栈执行 promise 回调 resolve():

  1. setTimeout(function timeout() { 
  2.   console.log('Timed out!'); 
  3. }, 0); 
  4.  
  5. Promise.resolve(1).then(function resolve() { 
  6.   console.log('Resolved!');}); 

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

Event Loop

(5) 最后,事件循环把计时器回调 timeout() 从任务队列中移出到调用栈中。然后调用栈执行计时器回调timeout():

  1. setTimeout(function timeout() { 
  2.   console.log('Timed out!');}, 0); 
  3.  
  4. Promise.resolve(1).then(function resolve() { 
  5.   console.log('Resolved!'); 
  6. }); 

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

Event Loop

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

总结

为什么立即解决的 promise 比立即计时器处理得更快?

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

 

责任编辑:赵宁宁 来源: 前端先锋
相关推荐

2021-01-21 07:53:29

面试官Promis打印e

2023-09-14 15:48:53

排序测试

2020-11-09 10:01:29

Python乘法位运算

2014-08-29 09:56:47

排序数组编程技巧

2022-11-10 15:32:29

2019-09-16 12:00:03

constC编程语言

2019-04-24 08:00:00

HTTPSHTTP前端

2015-07-31 16:29:15

DockerJavaLinux

2021-12-27 07:10:26

ClassmethodStaticmetho函数

2016-12-14 12:02:01

StormHadoop大数据

2017-02-14 14:20:02

StormHadoop

2013-08-15 10:05:16

代码

2023-09-20 00:06:30

Python代码函数

2024-06-26 10:50:35

2024-08-19 00:10:05

RocketMQ应用SDK

2020-03-18 14:08:48

Windows操作系统功能

2017-03-23 14:37:19

WebAssemblyasm.js编程

2021-05-12 08:15:53

HTTPSHTTP安全

2023-01-02 09:58:54

cdn响应X-Cache​​

2017-05-26 08:23:23

路由器WiFi重启
点赞
收藏

51CTO技术栈公众号