5

为什么 Promis 比setTimeout()更快?

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzI3NzIzMDY0NA%3D%3D&%3Bmid=2247497631&%3Bidx=1&%3Bsn=4ec38b3e2253caed934c0e762c6072c8
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
// 每日前端夜话 第468篇
// 正文共:1200 字
// 预计阅读时间:10 分钟

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

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!' 。立即解决的承诺比立即超时处理得更快。

是因为 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!'

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

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

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

事件循环

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

Vvu2Efm.png!mobile

空的事件循环

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

zaeEzqe.png!mobile

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

qu6FbuQ.png!mobile

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

zYZVrir.png!mobile

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

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

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

YzYNVv3.png!mobile

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

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

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

2YrQrmm.png!mobile

Event Loop

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

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

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

强力推荐前端面试刷题神器
rYNvUzy.gif!mobilezyyeqeA.png!mobile精彩文章回顾,点击直达 j6zmiq7.png!mobile

点分享

点收藏

点点赞

点在看


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK