nextTick queue
- 由 node 本身维护
- 通过
process.nextTick()
添加任务 - 可以递归调用,不会触发 call stack 的
RangeError: Maximum call stack size exceeded from v8
错误
应用场景
阻止进入事件循环,即在当前上下文之后,继续事件循环之前,执行操作
微任务并不能阻塞进入事件循环
执行回调函数
1
2
3
4
5
6
7
8
9
10
11
12const net = require('net');
const server = net.createServer(() => { }).listen(8080);
server.on('listening', () => {
// 这里是监听 'listening' 事件后执行的回调函数
console.log('已监听到!')
});
// 如果调用listen方法后,同步触发 listening 事件,此时尚未执行 `server.on`注册 listening 回调函数
// 导致无法监听到服务器启动事件。因此在实现上,将触发 listening 事件的操作放在 nextTick 队列中
// 在当前上下文执行完毕后(注册了 listening 回调),在其他 Promise 微任务之前,触发 listening 事件
// 确保回调函数注册完成,且不影响其他Promise异步操作1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16const EventEmitter = require('events');
class MyEmitter extends EventEmitter {
constructor() {
super();
// 等下文注册了监听函数后,再触发
process.nextTick(() => {
this.emit('event');
});
}
}
const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
console.log('an event occurred!');
});发出错误、清理资源、再次尝试请求等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24function apiCall(arg, callback) {
if (typeof arg !== 'string') {
return process.nextTick(
callback,
new TypeError('argument should be string')
);
}
}
// 未进入事件循环,微任务无法执行
Promise.resolve().then(() => {
console.log('Promise callback execute in even-loop');
});
(async () => {
console.log('main start')
apiCall(1, (e) => {
throw e
})
console.log('main end')
})()
// main start
// main end
// TypeError: argument should be string
执行顺序
可阻止进入事件循环,
进入前
当前上下文执行完后执行
进入后
在事件循环各阶段之间,microtask之前执行
vs 同步代码
- 在当前上下文,同步代码执行完后,进入事件循环之前,立即执行
vs microtask
下面的规律基于 commonjs 模块系统,esm 模块系统中先执行microtask
在 main 或 timer、nextTick回调中同时注册 nextTick 和 microtask,先执行 nextTick 的回调
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18setTimeout(() => {
console.log('start');
Promise.resolve().then(() => {
console.log('Promise callback1 executed');
})
process.nextTick(() => {
console.log('NextTick callback executed');
});
Promise.resolve().then(() => {
console.log('Promise callback2 executed');
})
console.log('end')
}, 0);
// start
// end
// NextTick callback executed
// Promise callback1 executed
// Promise callback2 executed在 microtask 回调中同时注册 nextTick 和 microtask,先执行 microtask,队列清空后再立即执行 nextTick(事件循环进入下一阶段之前)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18Promise.resolve().then(() => {
console.log('start');
Promise.resolve().then(() => {
console.log('Promise callback1 executed');
})
process.nextTick(() => {
console.log('NextTick callback executed');
});
Promise.resolve().then(() => {
console.log('Promise callback2 executed');
})
console.log('end')
});
// start
// end
// Promise callback1 executed
// Promise callback2 executed
// NextTick callback executed
vs 事件循环
在事件循环的每个阶段之间执行,直至 nextTick queue 和 microtask queue 为空
在 nextTick 中添加的 promise,或 promise 中添加的 nextTick 也会执行完再进入下一个阶段
timer(setTimeout、setInterval)
node 10,先执行完 timer 队列,再执行 nextTick 和 microtask
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16setTimeout(() => {
console.log('timer1')
Promise.resolve().then(function () {
console.log('promise1')
})
}, 0)
setTimeout(() => {
console.log('timer2')
Promise.resolve().then(function () {
console.log('promise2')
})
}, 0)
// node 10 输出
// timer1 timer2 promise1 promise2
// node >10 输出
// timer1 promise1 timer2 promise2node 11 以后,每个 timer 执行之后都会先依次执行完 nextTick queue 和 microtask queue,再执行第二个 timer,即使后面的timer已经达到。
在 nextTick 中添加的 promise,或 promise 中添加的 nextTick 也会执行完再执行第二个 timer
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
37console.log('main module start')
Promise.resolve().then(function () {
console.log('promise1')
})
process.nextTick(() => {
console.log('NextTick callback1 executed');
});
setTimeout(() => {
console.log('timer1')
Promise.resolve().then(function () {
console.log('promise2')
})
process.nextTick(() => {
console.log('NextTick callback2 executed');
});
}, 0)
setTimeout(() => {
console.log('timer2')
Promise.resolve().then(function () {
console.log('promise3')
})
process.nextTick(() => {
console.log('NextTick callback3 executed');
});
}, 0)
console.log('main module end')
// 输出结果为
// main module start
// main module end
// NextTick callback1 executed
// promise1
// timer1
// NextTick callback executed
// promise1
// timer2
// NextTick callback executed
// promise2
vs Promise
创建一个 promise 实例会分配3个闭包,大量未完成的promise会占用较多内存
- 执行器函数 executor
- resolve 参数
- reject 参数
microtask queue
由 v8 维护
通过 Promise 和
queueMicrotask()
添加任务两种方式添加的微任务类型相同,按注册顺序执行
应用场景
主要用于异步获取操作结果
不阻塞主线程
将操作放在当前微任务队列中异步执行
不阻塞父级上下线文中同步代码的执行
异步调用当前操作,且父级上下文中存在同步代码
如果是await同步调用,无论怎样,当前异步操作中的回调都会先于父级上下文await下面的回调
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
28async function load(key) {
const path = pathModule.join(__dirname, 'target.txt')
const hit = fs.existsSync(path)
console.log('is hit', hit)
if (hit === true) {
// queueMicrotask(() => {
// myEmitter.emit('load', 'load')
// })
myEmitter.emit('load', 'load')
return
}
await new Promise(res => {
fs.writeFile(path, 'data', { flag: 'w' }, () => {
res()
myEmitter.emit('load', 'load')
})
})
}
myEmitter.on('load', () => console.log('loaded data'))
async function processData(key) {
console.log('start...')
load(key) // 异步调用
// await load(key)
console.log('finish...') // 父级上下文中的同步代码
}维持操作的执行顺序
当异步操作中存在同步分支时,将同步分支的操作异步执行。nextTick 也可以实现,但是先于所有 promise 执行
使接口要么100%同步要么100%异步
1
2
3
4
5
6
7
8
9
10
11
12
13DataHandler.prototype.load = async function load(key) {
const hit = this._cache.get(key);
if (hit !== undefined) {
queueMicrotask(() => {
this.emit('load', hit); // 确保 load 事件总是异步触发
});
return;
}
const data = await fetchData(key);
this._cache.set(key, data);
this.emit('load', data); // 异步触发
};
优化并发控制
事件循环的一个迭代期内对异步操作的多次调用,在一个微任务中批量执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const messageQueue = [];
let sendMessage = (message) => {
messageQueue.push(message);
// 只有第一个加入队列的消息会注册一个微任务,后续消息直接加入队列
if (messageQueue.length === 1) {
// 在微任务注册到执行这段时间内发送的消息,通过一个微任务执行
queueMicrotask(() => {
const json = JSON.stringify(messageQueue);
// 微任务执行后,将队列清空
messageQueue.length = 0;
fetch("url-of-receiver", json);
});
}
};参考
执行顺序
vs 同步代码
- 同步代码执行完毕,退出ECStack,进入事件循环后执行
vs nextTick
[参考](#vs microtask)
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
61console.log('main start')
process.nextTick(() => {
console.log('register in main, execute nextTick')
})
queueMicrotask(() => {
console.log('register in main, execute queueMicrotask')
})
setTimeout(() => {
queueMicrotask(() => {
console.log('register in timer, execute queueMicrotask 2')
})
process.nextTick(() => {
console.log('register in timer, execute nextTick 2')
})
}, 0)
queueMicrotask(() => {
queueMicrotask(() => {
console.log('register in microtask, execute queueMicrotask 2')
})
process.nextTick(() => {
console.log('register in microtask, execute nextTick 2')
})
})
process.nextTick(() => {
queueMicrotask(() => {
console.log('register in nextTick, execute queueMicrotask 3')
})
process.nextTick(() => {
console.log('register in nextTick, execute nextTick 3')
})
})
console.log('main end')
// commonjs 模块中
// main start
// main end
// register in main, execute nextTick
// register in nextTick, execute nextTick 3
// register in main, execute queueMicrotask
// register in nextTick, execute queueMicrotask 3
// register in microtask, execute queueMicrotask 2
// register in microtask, execute nextTick 2
// register in timer, execute nextTick 2
// register in timer, execute queueMicrotask 2
// esm 模块, package.json 中添加 "type": "module",
// main start
// main end
// register in main, execute queueMicrotask
// register in microtask, execute queueMicrotask 2
// register in main, execute nextTick
// register in microtask, execute nextTick 2
// register in nextTick, execute nextTick 3
// register in nextTick, execute queueMicrotask 3
// register in timer, execute nextTick 2
// register in timer, execute queueMicrotask 2
vs 事件循环
- [参考](# vs 事件循环)