如何用织梦猫做网站和后台/自媒体
事件轮询机制
事件执行顺序:
引入一下两个概念:
- 宏任务(Macrotasks):就是参与了浏览器事件循环的异步任务
宏任务有:setTimeout,setInterval - 微任务(Microtasks): 直接在 Javascript 引擎中的执行的,没有参与浏览器事件循环的任务。
微任务:promise的then,await后面的语句,process.nextTick
执行的顺序是**‘先同后异,先微后宏’**
事件轮询机制阶段:
- 定时器:本阶段执行已经被 setTimeout() 和 setInterval() 的调度回调函数。
- 待定回调:执行延迟到下一个循环迭代的 I/O 回调。
- idle, prepare:仅系统内部使用。
- 轮询:检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和 setImmediate() 调度的之外),其余情况 node 将在适当的时候在此阻塞。
- 检测:setImmediate() 回调函数在这里执行。
- 关闭的回调函数:一些关闭的回调函数,如:socket.on(‘close’, …)。
定时器:本阶段执行已经被 setTimeout() 和 setInterval() 的调度回调函数。
会在轮询阶段最后执行
待定回调:执行延迟到下一个循环迭代的 I/O 回调。
系统操作的回调
idle, prepare:仅系统内部使用。
轮询:检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和 setImmediate() 调度的之外),其余情况 node 将在适当的时候在此阻塞。
- 队列不为空:循环访问回调队列,并同步执行
- 队列为空:
- 有setImmidiate(cb),结束轮询,到达检查阶段执行setImmidiate的cb
- 无setImmidiate(cb),被加入队列,并同步执行事件。
无setImmidiate(cb)举例:
一般情况下是这样
一个的时候:
new Promise((resolve, reject) => { console.log(1); resolve(2)}).then(data => console.log(data))
第一次进入轮询阶段,轮询队列为空 && 无setImmidiate,则then
会被加入到队列,console.log(1); resolve(1)
会被执行。then需要等待下次轮询。
到第二次进入轮询阶段,则队列里面有then
,则调用then的回调data=>console.log(data)
多个的时候:
new Promise((resolve, reject) => {console.log(1)resolve(2)
}).then(console.log)new Promise((resolve, reject) => {console.log(5)resolve(6)
}).then(data => {console.log(data)new Promise((resolve, reject) => {console.log(7)resolve(8)}).then(console.log)new Promise((resolve, reject) => {console.log(9)resolve(10)}).then(console.log)
})new Promise((resolve, reject) => {console.log(3)resolve(4)
}).then(console.log)console.log(0)
以上很可看出执行顺序是:
第一次轮询:同步:1,5,3,0进入微任务:6,4,进入下一次轮询进入微任务:8,10
有setImmidiate(cb)举例:
setImmediate(console.log, 0)new Promise((resolve, reject) => {console.log(11)resolve(12)
}).then(console.log)function asyncFn(cb) {setImmediate(cb, 3)
}
asyncFn(data => {new Promise((resolve, reject) => {console.log(data)resolve(6)}).then(console.log)new Promise((resolve, reject) => {console.log(7)resolve(8)}).then(console.log)
})setImmediate(console.log, 4)new Promise((resolve, reject) => {console.log(13)resolve(14)
}).then(console.log)console.log(5)
同步:11,13,5
异步:进入轮询阶段队列不为空,执行回调:12,14队列为空:执行setImmediate
结束轮询
检查阶段:0
进入轮询阶段队列为空
结束轮询
检查阶段:3,7
进入轮询阶段队列不为空:6,8轮询为空:执行setImmediate
结束轮询
检查阶段:4
这里跟promise的区别就是setImmediate会被轮询任务卡住,只有不为空的情况下才会被执行,其顺序是在每层轮询后才会被执行。
检测:setImmediate() 回调函数在这里执行。
执行回调
关闭的回调函数:一些关闭的回调函数,如:socket.on(‘close’, …)。
如果套接字或处理函数突然关闭(例如 socket.destroy()),则’close’ 事件将在这个阶段发出。否则它将通过 process.nextTick() 发出。
关注的问题:
process.nextTick() vs setImmediate() vs setTimeout()
从功能上来讲,其实setImmidiate的效果是下一次轮询前的执行,即nextTick。而process.nextTick的效果是在轮询阶段执行,刚好是Immediate的意思。这两者的名字应该是调转过来才对。
setTimeout: 最后执行
异步中任务顺序:
setImmediate(console.log, 0)// 关注这两个
setTimeout(console.log, 0, 999)
setTimeout(console.log, 10, 1000)new Promise((resolve, reject) => {console.log(11)resolve(12)
}).then(console.log)function asyncFn(cb) {setImmediate(cb, 3)
}
asyncFn(data => {new Promise((resolve, reject) => {console.log(data)process.nextTick(console.log, 119)resolve(6)}).then(console.log)new Promise((resolve, reject) => {console.log(7)process.nextTick(console.log, 229)resolve(8)}).then(console.log)
})setImmediate(console.log, 4)new Promise((resolve, reject) => {console.log(13)resolve(14)
}).then(console.log)
/* 1113533912149990371192296841000
*/
setTimeout是看其阈值情况而定,如果小于其阈值,比如为0,是在一次轮询后立即执行
promise.then = await的下一条语句 > process.nextTick > setTimeout > setImmediate如果大于其阈值,比如设置300,则是多次轮询后,最后执行
promise.then = await的下一条语句 > process.nextTick > setImmediate > ...多轮 > setTimeout
以上就是为什么会在代码中经常看到使用setTimeout(fm, 0)
或者setTimeout(fm, 300)
的原因。
建议:
-
开发人员在所有情况下都使用 setImmediate(),因为它更容易理解
-
使用 setImmediate() 相对于setTimeout() 的主要优势是,如果setImmediate()是在 I/O 周期内被调度的,那它将会在其中任何的定时器之前执行,跟这里存在多少个定时器无关
我要注意什么事情?
如果运行以下不在 I/O 周期(即主模块)内的脚本,则执行两个计时器的顺序是非确定性的,因为它受进程性能的约束:
I/O 循环内调用,setImmediate 总是被优先调用:
比如:
setTimeout(() => {console.log('timeout');
}, 0);
setImmediate(() => {console.log('immediate');
});
为什么要使用 process.nextTick()?
有两个主要原因:
-
允许用户处理错误,清理任何不需要的资源,或者在事件循环继续之前重试请求。
-
有时有让回调在栈展开后,但在事件循环继续之前运行的必要。
例子1:
const EventEmitter = require('events');
const util = require('util');
function MyEmitter() {EventEmitter.call(this);// 执行构造函数,出发event事件但监听的代码还没执行到,触发的事件并未被监听到,下面的代码不会被执行。// this.emit('event');// 捕获到错误,并加入事件队列放到下次轮询中调用process.nextTick(() => {this.emit('event');})
}
util.inherits(MyEmitter, EventEmitter);
const myEmitter = new MyEmitter();
myEmitter.on('event', () => {console.log('an event occurred!');
});
例子2:
const server = net.createServer();
server.on('connection', (conn) => { });
server.listen(8080);
server.on('listening', () => { });
这个例子效果同上,是我们常用常见的写法。其实一般我们会把.on
写在事件方式前,提前进行监听,这样才能捕捉到事件
参考:https://www.bookstack.cn/read/nodejs-guide/590a0c76ccf949ba.md#gxvmd