首先,js 不同宿主环境对 event loop 的实现是有区别的,这里说的都是 node.js 中的 event loop
关于 node.js 的 event loop ,之前写过两篇文章,最近回头看的时候,看不下去给删了
其实我的理解主要来源于:
之前不过是照虎画猫,写的太拉胯了,这里就不在分析和探究原理了。。
这里主要是用代码来验证一下 event loop 的执行顺序,以便回顾知识的时候更加直观。
下面的代码都在github仓库
setTimeout 和 setInterval 的执行顺序?
根据代码输出,可以知道,timers阶段的 setTimeout 和 setInterval 执行顺序是一个 FIFO 的队列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const time = 1000
setTimeout(() => { console.log('timeout1') }, time) const interval1 = setInterval(() => { console.log('interval1') clearInterval(interval1) }, time)
setTimeout(() => { console.log('timeout2') }, time) const interval2 = setInterval(() => { console.log('interval2') clearInterval(interval2) }, time)
|
setTimeout 和 setImmediate ?
分析代码和输出:
- 运行多次发现,
immediate callback 都在 timeout callback 先后顺序是随机的
- node.js里面
setTimeout(fn, 0)会被强制改为setTimeout(fn, 1)
- When
delay is larger than 2147483647 or less than 1, the delay will be set to 1. Non-integer delays are truncated to an integer.
- 官方文档说明
- 所以说它们的执行顺序,取决于同步代码的耗时,如果执行到timers时,刚好过去1ms,那么就会先输出
timeout callback ,反之
1 2 3 4 5 6 7 8 9 10
| setImmediate(function () { console.log('immediate callback') }) setTimeout(function () { console.log('timeout callback') }, 0)
|
poll 的 I/O callback 和 check 阶段的 setImmediate
分析代码和输出:
open empty callback 始终第一个输出,open file callback 和 immediate callback 先后顺序会变化
执行顺序取决于 fs.open(…) 的耗时。
1 2 3 4 5 6 7 8 9 10 11 12
| const fs = require('fs')
setImmediate(() => { console.log('immediate callback') }) fs.open('', 1, function () { console.log('open empty callback') })
fs.open('./不要混淆nodejs和浏览器中的event loop', 1, function () { console.log('open file callback') })
|
输出:
1 2 3 4 5 6 7
| open empty callback immediate callback open file callback
open empty callback open file callback immediate callback
|
close callbacks 阶段和 check 阶段
这个执行顺序没什么好说的,和文档描述的一致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const fs = require('fs')
const readStream = fs.createReadStream( __dirname + '/不要混淆nodejs和浏览器中的event loop.md' ) readStream.close() readStream.on('close', function () { console.log('close callback') })
setImmediate(function () { console.log('immediate callback') })
|
process.nextTick()
上面大佬的文章中从源码层面分析了 process.nextTick() 的执行时机, 即每个阶段结束都会执行。
可以看到输出中,每个 process.nextTick() 都是在该阶段结束立即执行。
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
| const fs = require('fs')
setTimeout(() => { console.log('timeout callback') process.nextTick(function () { console.log('timeout tick') }) }, 0)
setImmediate(function () { console.log('immediate callback') process.nextTick(function () { console.log('immediate tick') }) })
const readStream = fs.createReadStream( __dirname + '/不要混淆nodejs和浏览器中的event loop.md' ) readStream.close() readStream.on('close', function () { console.log('close callback') process.nextTick(function(){ console.log('close callback tick') }) })
fs.open('', 1, function () { console.log('open empty callback') process.nextTick(function () { console.log('open empty tick') }) })
|
输出:
1 2 3 4 5 6 7 8
| timeout callback timeout tick open empty callback open empty tick immediate callback immediate tick close callback close callback tick
|
microtasks 和 process.nextTick()
分析一下:
- nextTick 和 microtasks 都是一个 FIFO 的队列
- microtasks 会在执行完 nextTick 之后执行
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
| const fs = require('fs')
process.nextTick(function () { console.log('nextTick') new Promise(function (resolve) { resolve() }).then(function () { console.log('microtasks2') }) })
new Promise(function (resolve) { resolve() }).then(function () { console.log('microtasks') })
process.nextTick(function () { console.log('nextTick2') new Promise(function (resolve) { resolve() }).then(function () { console.log('microtasks3') }) }) fs.open('', 1, function () { console.log('====我是分界线====') })
|
输出:
1 2 3 4 5 6
| nextTick nextTick2 microtasks microtasks2 microtasks3 ====我是分界线====
|
制造死循环
下面两段代码都可以制造死循环。。
1 2 3 4 5 6 7 8 9 10
| const fs = require('fs') function nextTick () { process.nextTick(function () { nextTick() }) } nextTick() fs.open('', 1, function () { console.log('====永远都不会轮到我====') })
|
和
1 2 3 4 5 6 7 8 9 10 11 12 13
| const fs = require('fs') function runMicrotask(){ new Promise(function (resolve) { resolve() }).then(function () { runMicrotask() }) } runMicrotask()
fs.open('', 1, function () { console.log('====永远都不会轮到我====') })
|