EvenLoop队列中的执行顺序

EvenLoop队列中的执行顺序

JiangWen Master

前言:谈谈 promise.resove,setTimeout,setImmediate,process.nextTick 在 EvenLoop 队列中的执行顺序

1. 问题的引出

event loop 都不陌生,是指主线程从 “任务队列” 中循环读取任务,比如

1
2
3
setTimeout(function(){console.log(1)},0);
console.log(2)
//输出2,1

在上述的例子中,我们明白首先执行主线程中的同步任务,当主线程任务执行完毕后,再从 event loop 中读取任务,因此先输出 2,再输出 1。

event loop 读取任务的先后顺序,取决于任务队列(Job queue)中对于不同任务读取规则的限定。比如下面一个例子:

1
2
3
4
5
6
7
8
9
setTimeout(function () {
console.log(3);
}, 0);

Promise.resolve().then(function () {
console.log(2);
});
console.log(1);
//输出为 1 2 3

先输出 1,没有问题,因为是同步任务在主线程中优先执行,这里的问题是 setTimeout 和 Promise.then 任务的执行优先级是如何定义的。

2 . Job queue 中的执行顺序

在 Job queue 中的队列分为两种类型:微任务 (macro-task) 和宏任务 (micro-task)。我们举例来看执行顺序的规定,我们设

macro-task 队列包含任务: a1, a2 , a3
micro-task 队列包含任务: b1, b2 , b3

执行顺序为,首先执行 marco-task 队列开头的任务,也就是 a1 任务,执行完毕后,在执行 micro-task 队列里的所有任务,也就是依次执行 b1, b2 , b3,执行完后清空 micro-task 中的任务,接着执行 marco-task 中的第二个任务,依次循环。

了解完了 macro-task 和 micro-task 两种队列的执行顺序之后,我们接着来看,真实场景下这两种类型的队列里真正包含的任务(我们以 node V8 引擎为例),在 node V8 中,这两种类型的真实任务顺序如下所示:

macro-task 队列真实包含任务:

script (主程序代码),setTimeout, setInterval, setImmediate, I/O, UI rendering

micro-task 队列真实包含任务:
process.nextTick, Promises, Object.observe, MutationObserver

由此我们得到的执行顺序应该为:

script (主程序代码)—>process.nextTick—>Promises…——>setTimeout——>setInterval——>setImmediate——> I/O——>UI rendering

在 ES6 中 macro-task 队列又称为 ScriptJobs,而 micro-task 又称 PromiseJobs

3 . 真实环境中执行顺序的举例

(1) setTimeout 和 promise

1
2
3
4
5
6
7
8
9
setTimeout(function () {
console.log(3);
}, 0);

Promise.resolve().then(function () {
console.log(2);
});

console.log(1);

我们先以第 1 小节的例子为例,这里遵循的顺序为:

script (主程序代码)——>promise——>setTimeout
对应的输出依次为:1 ——>2——>3

(2) process.nextTick 和 promise、setTimeout

1
2
3
4
5
6
7
8
9
10
11
12
setTimeout(function(){console.log(1)},0);

new Promise(function(resolve,reject){
console.log(2);
resolve();
}).then(function(){console.log(3)
}).then(function(){console.log(4)});

process.nextTick(function(){console.log(5)});

console.log(6);
//输出2,6,5,3,4,1

这个例子就比较复杂了,这里要注意的一点在定义 promise 的时候,promise 构造部分是同步执行的,这样问题就迎刃而解了。

首先分析 Job queue 的执行顺序:

script (主程序代码)——>process.nextTick——>promise——>setTimeout

I) 主体部分: 定义 promise 的构造部分是同步的,
因此先输出 2 ,主体部分再输出 6(同步情况下,就是严格按照定义的先后顺序)

II)process.nextTick: 输出 5

III)promise: 这里的 promise 部分,严格的说其实是 promise.then 部分,输出的是 3,4

IV) setTimeout : 最后输出 1

综合的执行顺序就是: 2——>6——>5——>3——>4——>1

(3) 更复杂的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
setTimeout(function(){console.log(1)},0);

new Promise(function(resolve,reject){
console.log(2);
setTimeout(function(){resolve()},0)
}).then(function(){console.log(3)
}).then(function(){console.log(4)});

process.nextTick(function(){console.log(5)});

console.log(6);

//输出的是 2 6 5 1 3 4

这种情况跟我们(2)中的例子,区别在于 promise 的构造中,没有同步的 resolve,因此 promise.then 在当前的执行队列中是不存在的,只有 promise 从 pending 转移到 resolve,才会有 then 方法,而这个 resolve 是在一个 setTimout 时间中完成的,因此 3,4 最后输出。

文章转载来源:: https://github.com/forthealllight/blog/issues/5

  • 标题: EvenLoop队列中的执行顺序
  • 作者: JiangWen
  • 创建于 : 2023-03-28 12:15:00
  • 更新于 : 2024-11-24 17:45:51
  • 链接: https://blog.jiangwen.site/2023/03/28/Javascript/EvenLoop队列中的执行顺序/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.2.2