乐趣区

关于javascript:为什么-Promise-比setTimeout-更快

试验

先做一个试验:来看看立刻解决的 Promise 和立刻超时(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 的工作原理。

调用栈(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!' 被输入到控制台。

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

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

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

此时调用栈为空。脚本执行已实现。

总结

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

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


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

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


欢送持续浏览本专栏其它高赞文章:

  • 深刻了解 Shadow DOM v1
  • 一步步教你用 WebVR 实现虚拟现实游戏
  • 13 个帮你进步开发效率的古代 CSS 框架
  • 疾速上手 BootstrapVue
  • JavaScript 引擎是如何工作的?从调用栈到 Promise 你须要晓得的所有
  • WebSocket 实战:在 Node 和 React 之间进行实时通信
  • 对于 Git 的 20 个面试题
  • 深刻解析 Node.js 的 console.log
  • Node.js 到底是什么?
  • 30 分钟用 Node.js 构建一个 API 服务器
  • Javascript 的对象拷贝
  • 程序员 30 岁前月薪达不到 30K,该何去何从
  • 14 个最好的 JavaScript 数据可视化库
  • 8 个给前端的顶级 VS Code 扩大插件
  • Node.js 多线程齐全指南
  • 把 HTML 转成 PDF 的 4 个计划及实现

  • 更多文章 …
退出移动版