We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
一直以来对Node.js 的事件循环机制都像蒙了层纱,看过一些文章,也经常只是跑通几个例子,然后强行解释一波,始终无法真正的理解。今天闲逛时看到了这篇文章,感觉讲的很透彻,但是只是看完估计很快就忘了,所以这篇博客就记录下我对这篇文章的理解。
process.nextTick()
上面这个图展示了事件循环的机制,绿色的块是macrotask(宏任务),macrotask 中穿插的粉红箭头是 microtask(微任务)。
宏任务主要有:整体代码(script)、setTimeout、setInterval、I/O、setImmediate
把那个图正过来是下面的样子:
┌───────────────────────┐ ┌─>│ timers │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ I/O callbacks │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ idle, prepare │ │ └──────────┬────────────┘ ┌───────────────┐ │ ┌──────────┴────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └──────────┬────────────┘ │ data, etc. │ │ ┌──────────┴────────────┐ └───────────────┘ │ │ check │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ └──┤ close callbacks │ └───────────────────────┘
event loop的每一次循环都需要依次经过上述的阶段。 每个阶段都有自己的callback队列,每当进入某个阶段,都会从所属的队列中取出callback来执行,当队列为空或者被执行callback的数量达到系统的最大数量时,进入下一阶段。这六个阶段都执行完毕称为一轮循环。
微任务主要有:Promise.then、process.nextTick
事件循环的每个阶段都会先执行微任务。
这里指的是 then 中的func 是一个微任务,但是Promise 的构造函数是同步执行的。
这里的题目主要是来源于知乎专栏和这里
这些题目对我理解上面的模型有很大帮助。
setTimeout(() => { console.log('timeout'); }, 0) setImmediate(() => { console.log('immediate'); }) // immediate // timeout 或 // timeout // immediate
第一个循环:
这样看,答案应该是确定的。
但是虽然 timers 阶段在 check 阶段前面,可还有一个问题。
setTimeout/setInterval 的第二个参数取值范围为[1, 2^31-1],如果不在这个范围则置为1,所以 setTimeout(func, 0),实际上是 setTimeout(func, 1)
setTimeout(() => { console.log('timeout'); }, 0) setImmediate(() => { console.log('immediate'); }) let time = Date.now() while(Date.now() - time < 10); // timeout // immediate
通过while循环使下个事件循环到达 timer阶段的时间距离注册 setTimeout 的回调的时间一定大于 10ms,这样就肯定会先执行 setTimeout
const fs = require('fs') fs.readFile(__filename, () => { setTimeout(() => { console.log('setTimeout') }, 0) setImmediate(() => { console.log('setImmediate') }) }) // setImmediate // setTimeout
不知道第几个循环:
之后的一个循环:
setInterval(() => { console.log('setInterval') }, 100) process.nextTick(function tick () { process.nextTick(tick) }) // 什么都不打印
process.nextTick 内执行 process.nextTick 仍然将 tick 函数注册到当前 microtask 的尾部,所以导致 microtask 永远执行不完。导致 event loop 上其他 macrotask 阶段的回调函数没有机会执行。
setInterval(() => { console.log('setInterval') }, 100) setImmediate(function immediate () { setImmediate(immediate) }) // 每 100ms 打印一次 setInterval
这是因为 setImmediate 中的setImmediate 会将 immediate 函数注册到下一次 event loop 的 check 阶段,而不是当前正在执行的 check 阶段,所以给了 event loop 上其他 macrotask 执行的机会。
setImmediate(() => { console.log('setImmediate1') setImmediate(() => { console.log('setImmediate2') }) process.nextTick(() => { console.log('nextTick') }) }) setImmediate(() => { console.log('setImmediate3') }) // setImmediate1 // setImmediate3 // nextTick // setImmediate2
第二个循环:
第三个循环:
const promise = Promise.resolve() .then(() => { return promise }) promise.catch(console.error) // TypeError: Chaining cycle detected for promise #<Promise> process.nextTick(function tick () { process.nextTick(tick) })
promise.then 类似于 process.nextTick,都会将回调函数注册到 microtask 阶段。上面代码会导致死循环。
上面两段代码效果一样。
Promise.resolve() .then(() => { console.log('promise'); }) process.nextTick(() => { console.log('process'); }) // process // promise
promise.then 虽然和 process.nextTick 一样,都将回调函数注册到 microtask,但优先级不一样。process.nextTick 的 microtask 队列 总是优先于 promise 的 microtask 队列 执行。
setTimeout(() => { console.log(1) }, 0) new Promise((resolve, reject) => { console.log(2) for (let i = 0; i < 10000; i++) { i === 9999 && resolve() } console.log(3) }).then(() => { console.log(4) }) setImmediate(() => { console.log(6) }) console.log(5) // 2 // 3 // 5 // 4 // 1 // 6
setImmediate(() => { console.log(1) setTimeout(() => { console.log(2) }, 100) setImmediate(() => { console.log(3) }) process.nextTick(() => { console.log(4) }) }) process.nextTick(() => { console.log(5) setTimeout(() => { console.log(6) }, 100) setImmediate(() => { console.log(7) }) process.nextTick(() => { console.log(8) }) }) console.log(9) // 9 // 5 // 8 // 1 // 7 // 4 // 3 // 6 // 2
new Promise(resolve => { resolve(1); Promise.resolve().then(() => console.log(2)); console.log(4) }).then(t => console.log(t)); console.log(3); // 4 // 3 // 2 // 1
Promise.resolve
Promise.resolve().then(() => { console.log(5); }) new Promise(resolve => { resolve(1); Promise.resolve().then(() => { console.log(2) }); console.log(4) }).then(t => console.log(t)); process.nextTick(() => { console.log(7); }) Promise.resolve().then(() => { console.log(6); }) console.log(3); // 4 // 3 // 7 // 5 // 2 // 1 // 6
第一个循环:
new Promise(resolve => { resolve(1); Promise.resolve().then(() => { console.log(2) process.nextTick(() => { console.log(5); }); }); Promise.resolve().then(() => { console.log(6); }) console.log(4) }).then(t => console.log(t)); console.log(3); // 4 // 3 // 2 // 6 // 1 // 5
事件循环模型。
事件循环的每个阶段都会先执行微任务
细节:
The text was updated successfully, but these errors were encountered:
No branches or pull requests
前言
一直以来对Node.js 的事件循环机制都像蒙了层纱,看过一些文章,也经常只是跑通几个例子,然后强行解释一波,始终无法真正的理解。今天闲逛时看到了这篇文章,感觉讲的很透彻,但是只是看完估计很快就忘了,所以这篇博客就记录下我对这篇文章的理解。
一. Node.js 的 event loop
1.1 Node.js 与 浏览器的 event loop 区别
1.2 参考文档
process.nextTick()
二. Node.js event loop 的运行机制
上面这个图展示了事件循环的机制,绿色的块是macrotask(宏任务),macrotask 中穿插的粉红箭头是 microtask(微任务)。
2.1 宏任务
宏任务主要有:整体代码(script)、setTimeout、setInterval、I/O、setImmediate
把那个图正过来是下面的样子:
event loop的每一次循环都需要依次经过上述的阶段。 每个阶段都有自己的callback队列,每当进入某个阶段,都会从所属的队列中取出callback来执行,当队列为空或者被执行callback的数量达到系统的最大数量时,进入下一阶段。这六个阶段都执行完毕称为一轮循环。
2.2 微任务
微任务主要有:Promise.then、process.nextTick
事件循环的每个阶段都会先执行微任务。
Promise.then(func)
这里指的是 then 中的func 是一个微任务,但是Promise 的构造函数是同步执行的。
三. 题目加深理解
这里的题目主要是来源于知乎专栏和这里
这些题目对我理解上面的模型有很大帮助。
3.1 题目一
第一个循环:
这样看,答案应该是确定的。
但是虽然 timers 阶段在 check 阶段前面,可还有一个问题。
setTimeout/setInterval 的第二个参数取值范围为[1, 2^31-1],如果不在这个范围则置为1,所以 setTimeout(func, 0),实际上是 setTimeout(func, 1)
通过while循环使下个事件循环到达 timer阶段的时间距离注册 setTimeout 的回调的时间一定大于 10ms,这样就肯定会先执行 setTimeout
3.2 题目二
第一个循环:
不知道第几个循环:
之后的一个循环:
3.3 题目三
process.nextTick 内执行 process.nextTick 仍然将 tick 函数注册到当前 microtask 的尾部,所以导致 microtask 永远执行不完。导致 event loop 上其他 macrotask 阶段的回调函数没有机会执行。
这是因为 setImmediate 中的setImmediate 会将 immediate 函数注册到下一次 event loop 的 check 阶段,而不是当前正在执行的 check 阶段,所以给了 event loop 上其他 macrotask 执行的机会。
第一个循环:
第二个循环:
第三个循环:
3.4 题目四
promise.then 类似于 process.nextTick,都会将回调函数注册到 microtask 阶段。上面代码会导致死循环。
上面两段代码效果一样。
promise.then 虽然和 process.nextTick 一样,都将回调函数注册到 microtask,但优先级不一样。process.nextTick 的 microtask 队列 总是优先于 promise 的 microtask 队列 执行。
3.5 题目五
第一个循环:
3.6 题目六
第一个循环:
第二个循环:
第三个循环:
不知道第几个循环:
3.7 题目七
第一个循环:
Promise.resolve
将 then 的函数注册在 microTask 队列中,打印4,第一个循环:
第一个循环:
四.要点总结
事件循环模型。
事件循环的每个阶段都会先执行微任务
细节:
五. 参考
The text was updated successfully, but these errors were encountered: