消息队列(message queue)里的任务也被称作宏任务(macrotask)
I/O操作、fetch、event(onClick)、渲染任务都是宏任务
工作队列(job queue)里的任务也被称作微任务(microtask)
MutationObserver、和Promise属于微任务
process.nextTick()和setImmediate()是node独有的插队方法
以下图片、例子均来自node官网
调用栈(call stack)
众所周知,js是单线程事件驱动的语言.它自上而下执行, 把遇到的函数压入调用栈(call stack), 然后按顺序执行.
举个例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
const bar = () => console.log('bar')
const baz = () => console.log('baz')
const foo = () => {
console.log('foo')
bar()
baz()
}
foo()
// 输出
// foo
// bar
// baz
|
调用顺序如下
消息队列(message queue)
消息队列会在每次清空调用栈后, 工作队列清空后才执行
例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
const bar = () => console.log('bar')
const baz = () => console.log('baz')
const foo = () => {
console.log('foo')
setTimeout(bar, 0)
baz()
}
foo()
// 输出
// foo
// baz
// bar
|
执行顺序
工作队列(job queue)
工作队列会在调用栈后, 消息队列前执行, 它在当间
例子
因为node官网没画图, 我这里写上注释
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
const bar = () => console.log("bar");
const baz = () => console.log("baz");
const foo = () => {
// 2. 顺序执行
console.log("foo");
new Promise((resolve, reject) => {
// 3. new Promise里面是同步的
console.log(1);
// 4. resolve进入工作队列(job queue)
resolve("should be right after baz, before bar");
// 5. 顺序执行
console.log(2);
return 3;
})
// 6. then 进入工作队列(job queue)
.then((resolve) =>
// 10. 调用栈结束, 开始执行微任务(microtask), 清空工作队列(job queue)
console.log(resolve)
)
// 7. then 进入工作队列(job queue)
.then(() => {
// 11. 执行第二个微任务(micro task)
console.log("second job queue");
// 12. 压入第二个宏任务(macro task)到消息队列(message task)
setTimeout(() => {
// 14. 执行第二个宏任务
console.log("second message job");
}, 0);
});
// 8. 压入消息队列(message queue)
setTimeout(
// 13. 任务队列清空, 执行第一个宏任务
bar,
0);
// 9. 顺序执行
baz();
};
// 1. 调用栈从这里进去
foo();
//输出
// foo
// 1
// 2
// baz
// should be right after baz, before bar
// second job queue
// bar
// second message job
|
process.nextTick()
解释
Every time the event loop takes a full trip, we call it a tick.
每次事件循环进行一整趟时,我们都将其称为tick。
When we pass a function to process.nextTick(),
当我们将一个函数传递给process.nextTick()时,
we instruct the engine to invoke this function at the end of the current operation, before the next event loop tick starts:
我们指示引擎在下一个事件循环开始之前,在当前操作结束后调用此函数:
我测试下来, 他的优先级大于微任务, 会在微任务之前执行, 然后执行完所有内部代码, 有趣的是, 它内部如果有微任务, 那么会和外部的微任务交替插入. 具体行为, 请复制下面的代码自己跑一跑, 我现在脑壳疼.
例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
|
process.nextTick(() => {
console.log("1 tick");
setTimeout(() => {
console.log("in tick macro task")
}, 0);
new Promise((res) => res())
// Promise.resolve()
.then(() => console.log("in tick micro task1"))
.then(() => console.log("in tick micro task2"));
process.nextTick(()=>{
console.log('in tick')
})
});
const bar = () => {
console.log("bar");
// new Promise((res) => res())
Promise.resolve()
.then(() => console.log("anthor micro task1"))
.then(() => console.log("anthor micro task2"));
process.nextTick(() => {
console.log("6 tick");
});
};
const baz = () => console.log("baz");
const foo = () => {
console.log("foo");
new Promise((resolve, reject) => {
process.nextTick(() => {
console.log("2 tick");
});
console.log(1);
resolve("should be right after baz, before bar");
console.log(2);
return 3;
})
.then((resolve) => {
process.nextTick(() => {
console.log("4 tick");
});
console.log(resolve);
})
.then(() => {
console.log("second job queue");
process.nextTick(() => {
console.log("5 tick");
});
setTimeout(() => {
process.nextTick(() => {
console.log("7 tick");
});
console.log("second message job");
}, 0);
});
setTimeout(bar, 0);
baz();
};
foo();
process.nextTick(() => {
console.log("3 tick");
});
// 输出
// foo
// 1
// 2
// baz
// 1 tick
// 2 tick
// 3 tick
// in tick
// should be right after baz, before bar
// in tick micro task1
// second job queue
// in tick micro task2
// 4 tick
// 5 tick
// bar
// 6 tick
// anthor micro task1
// anthor micro task2
// in tick macro task
// second message job
// 7 tick
|
解释
简单的说setImmediate会在任何I/O操作之后执行, 以下抄自stack overflow的解答 ——
Use setImmediate if you want to queue the function behind whatever I/O event callbacks that are already in the event queue.
如果要将函数放在事件队列中已经存在的任何I / O事件回调后面,请使用setImmediate。
Use process.nextTick to effectively queue the function at the head of the event queue so that it executes immediately after the current function completes.
使用process.nextTick将函数有效地放在事件队列的开头,以便在当前函数完成后立即执行。
So in a case where you’re trying to break up a long running, CPU-bound job using recursion, you would now want to use setImmediate rather than process.nextTick to queue the next iteration as otherwise any I/O event callbacks wouldn’t get the chance to run between iterations.
因此,在您尝试使用递归分解长时间运行且受CPU限制的作业的情况下,您现在想使用setImmediate而不是process.nextTick将下一次迭代排队,因为否则所有I / O事件回调都不会没有机会在迭代之间运行。
例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
import fs from 'fs';
import http from 'http';
const options = {
host: 'www.stackoverflow.com',
port: 80,
path: '/index.html'
};
describe('deferredExecution', () => {
it('deferredExecution', (done) => {
console.log('Start');
setTimeout(() => console.log('TO1'), 0);
setImmediate(() => console.log('IM1'));
process.nextTick(() => console.log('NT1'));
setImmediate(() => console.log('IM2'));
process.nextTick(() => console.log('NT2'));
http.get(options, () => console.log('IO1'));
fs.readdir(process.cwd(), () => console.log('IO2'));
setImmediate(() => console.log('IM3'));
process.nextTick(() => console.log('NT3'));
setImmediate(() => console.log('IM4'));
fs.readdir(process.cwd(), () => console.log('IO3'));
console.log('Done');
setTimeout(done, 1500);
});
});
// 输出
// Start
// Done
// NT1
// NT2
// NT3
// TO1
// IO2
// IO3
// IM1
// IM2
// IM3
// IM4
// IO1
|
如果运行以下不在 I/O 周期(即主模块)内的脚本,则执行两个计时器的顺序是非确定性的,因为它受进程性能的约束;但是,如果你把这两个函数放入一个 I/O 循环内调用,setImmediate 总是被优先调用.
详情请看
参考资料
消息队列和事件循环、宏任务和微任务
The Node.js Event Loop
Understanding process.nextTick()
Understanding setImmediate()
setImmediate vs. nextTick