有这么一道题:
- setTimeout(function() {
- console.log(1)
- }, 0);
- new Promise(function(resolve, reject) {
- console.log(2)
- for (var i = 0; i <10000; i++) {
- if(i === 10) {console.log(10)}
- i == 9999 && resolve();
- }
- console.log(3)
- }).then(function() {
- console.log(4)
- })
- console.log(5);
结果是怎样的呢?
真正结果是: 2 10 3 5 4 1, 不知你写对了没有. 如果你的答案错了, 没关系, 接着往下读, 会为你一一解惑.
这里涉及到三个 "何时":
setTimeout(fn, 0)何时执行
promise 函数何时执行
then 何时执行
下面我们来一一分析.
1. setTimeout(fn, 0)何时执行?
我们知道, JavaScript 是基于事件驱动单线程执行的, 所有任务都需要排队, 也就是说前一个任务结束, 才会去执行下一个任务. 而像 settimeout,ajax 等异步操作的回调, 会进入 "任务队列" 中, 而且只有主线程中没有执行任何同步代码的前提下, 才会执行异步回调.
而 settimeout(fn, 0)表示立即执行, 也就是用来改变任务的执行顺序, 要求浏览器 "尽可能快" 的进行回调.
前面有写过一篇详细介绍 setTimeout 的文章: 你应该知道的 setTimeout 秘密 http://ghmagical.com/article/page/id/H61NOVU0RZ9Y
2. promise 何时执行?
我们依旧以上面代码为例:
- new Promise(function(resolve, reject) {
- console.log(2)
- for (var i = 0; i < 10000; i++) {
- if(i === 10) {console.log(10)}
- i == 9999 && resolve();
- }
- console.log(3)
- })
结果: 2 10 3
从结果可以看出, Promise 新建后立即执行, 也就是说,
Promise 构造函数
里的代码是同步执行的.
3. then 何时执行?
看看实例:
- new Promise(function(resolve, reject) {
- console.log(2)
- for (var i = 0; i < 10000; i++) {
- if(i === 10) {console.log(10)}
- i == 9999 && resolve();
- }
- console.log(3)
- }).then(function() {
- console.log(4)
- })
- for (var i = 0; i < 5; i++) {
- console.log('a' + i);
- }
结果:
2 10 3 a0 a1 a2 a3 a4 4
从结果来看, 可以知道 then 方法指向的回调将在当前脚本所有同步任务执行完后执行.
再来回顾第一个例子:
- setTimeout(function() {
- console.log(1)
- }, 0);
- new Promise(function(resolve, reject) {
- console.log(2)
- for (var i = 0; i < 10000; i++) {
- if(i === 10) {console.log(10)}
- i == 9999 && resolve();
- }
- console.log(3)
- }).then(function() {
- console.log(4)
- })
- console.log(5);
结果是: 2 10 3 5 4 1
通过上面的实例, 相信你已经解开了三个 "何时". 可能你还有一个疑惑, 那就是为什么 then 比 setTimeout 执行的要早呢?
目前有两种原因导致:
1) setTimeout 的 0 是否真的为 0?
其实, setTimeout 有个最小执行时间(minimum delay of 4ms https://developer.mozilla.org/en-US/docs/web/API/WindowOrWorkerGlobalScope/setTimeout#Reasons_for_delays_longer_than_specified ), 并不是 0s 执行的.
注: html5 中已经将最小执行时间统一为 4ms.
2) macrotask 与 microtask
Macrotasks 和 Microtasks 都属于异步任务中的一种, 常用 api 分类:
macrotasks:
setTimeout, setInterval, setImmediate, I/O, UI rendering
- microtasks:
- process.nextTick, Promise, MutationObserver
一个事件循环中只有一个 macrotask 任务, 可以有一个或多个 microtask 任务.
来看看上面实例的执行:
首先, setTimeout 被推进到 macrotask 队列 (将在下一个 macrotask 中执行) 中.
接着, 会先执行 macrotask 中的第一个任务(整个 script 中的同步代码 ), 再加上
promise 构造函数
也是同步的(promise.then 回调被推进到 microtask 队列中), 所以会先打印出 2 10 3, 然后继续执行末尾的, 打印出 5
此时, 已经执行完了第一个 macrotask , 所以接下来会顺序执行所有的 microtask, 也就是 promise.then 的回调函数, 从而打印出 4.
此时, microtask 队列中的任务已经执行完毕, 所以执行剩下的 macrotask 队列中的任务, 也就是 setTimeout, 所以打印出 1.
经过层层测试, 所以最终得出的结论是:
同步代码(包括 promise 的构造函数) -> promise.then -> setTimeout
如有错误或疑问, 欢迎在下方评论区留言!
参考文章:
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoop
- http://www.ruanyifeng.com/blog/2014/10/event-loop.html
juejin.im/entry/5779b...
- https://www.zhihu.com/question/36972010/answer/71338002
- https://developer.mozilla.org/zh-CN/docs/Web/API/Window/setTimeout
- https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
- https://stackoverflow.com/questions/38752620/promise-vs-settimeout
来源: https://juejin.im/entry/5afd78bff265da0b886da327