极简 Node.js 入门 - 2.4 定时器
2021-01-11 13:33
标签:通过 浏览器 connect eve uid 回调 五行 occurred mic 极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node/timer timer 用于安排函数在未来某个时间点被调用,Node.js 中的定时器函数实现了与 Web 浏览器提供的定时器 API 类似的 API,但是使用了事件循环实现,Node.js 中有四个相关的方法 看一个示例,用几种方法分别异步打印一个数字 会打印 5 4 3 2 1 或者 5 4 3 1 2 第五行是同步执行,其它都是异步的 所以先打印 5,这个很好理解,剩下的都是异步操作,Node.js 按照什么顺序执行呢? Node.js 启动后会初始化事件轮询,过程中可能处理异步调用、定时器调度和 process.nextTick(),然后开始处理event loop。官网中有这样一张图用来介绍?event loop 操作顺序 event loop 的每个阶段都有一个任务队列,当 event loop 进入给定的阶段时,将执行该阶段的任务队列,直到队列清空或执行的回调达到系统上限后,才会转入下一个阶段,当所有阶段被顺序执行一次后,称 event loop 完成了一个 tick 各个阶段主要任务 日常开发中绝大部分异步任务都是在 timers、poll、check 阶段处理的 Node.js 会在 timers 阶段检查是否有过期的 timer,如果存在则把回调放到 timer 队列中等待执行,Node.js 使用单线程,受限于主线程空闲情况和机器其它进程影响,并不能保证 timer 按照精确时间执行 Immediate 类型的计时器回调会在 check 阶段被调用,Timeout 计时器会在设定的时间过期后尽快的调用回调,但 多次执行会发现打印的顺序不一样 poll 阶段主要有两个任务 当event loop进入 poll 阶段且没有被调度的计时器时 在该阶段执行 setImmediate 回调 前端同学肯定都听说过 micoTask 和 macroTask,Promise.then 属于 microTask,在浏览器环境下 microTask 任务会在每个 macroTask 执行最末端调用 setImmediate 听起来是立即执行,process.nextTick 听起来是下一个时钟执行,为什么效果是反过来的?这就要从那段不堪回首的历史讲起 执行程序将返回 于是乎需要一个不这么 bug 的调用,setImmediate 方法出现了,比较令人费解的是在 process.nextTick 起错名字的情况下,setImmediate 也用了一个错误的名字以示区分。。。 那么是不是编程中应该杜绝使用? process.nextTick 呢?官方推荐大部分时候应该使用?setImmediate,同时对 process.nextTick 的最大调用堆栈做了限制,但 process.nextTick 的调用机制确实也能为我们解决一些棘手的问题 一个类继承了 EventEmitter,而且想在实例化的时候触发一个事件 在构造函数执行 极简 Node.js 入门 - 2.4 定时器 标签:通过 浏览器 connect eve uid 回调 五行 occurred mic 原文地址:https://www.cnblogs.com/dolphinX/p/13494437.html
前两个含义和 web 上的是一致的,后两个是 Node.js 独有的,效果看起来就是 setTimeout(callback, 0),在 Node.js 编程中使用的最多
Node.js 不保证回调被触发的确切时间,也不保证它们的顺序,回调会在尽可能接近指定的时间被调用。setTimeout?当 delay 大于 2147483647 或小于 1 时,则 delay 将会被设置为 1, 非整数的 delay 会被截断为整数奇怪的执行顺序
setImmediate(console.log, 1);
setTimeout(console.log, 1, 2);
Promise.resolve(3).then(console.log);
process.nextTick(console.log, 4);
console.log(5);
同步 & 异步
setImmediate(console.log, 1);
setTimeout(console.log, 1, 2);
Promise.resolve(3).then(console.log);
process.nextTick(console.log, 4);
/****************** 同步任务和异步任务的分割线 ********************/
console.log(5);
event loop
┌───────────────────────────┐
┌─>│ timers │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │
│ └─────────────┬─────────────┘ ┌───────────────┐
│ ┌─────────────┴─────────────┐ │ incoming: │
│ │ poll │
异步操作都被放到了下一个 event loop tick 中,process.nextTick 在进入下一次 event loop tick 之前执行,所以肯定在其它异步操作之前setImmediate(console.log, 1);
setTimeout(console.log, 1, 2);
Promise.resolve(3).then(console.log);
/****************** 下次 event loop tick 分割线 ********************/
process.nextTick(console.log, 4);
/****************** 同步任务和异步任务的分割线 ********************/
console.log(5);
timers
定时器主要有两种
setTimeout(() => {
console.log(‘timeout‘);
}, 0);
setImmediate(() => {
console.log(‘immediate‘);
});
poll
一旦 poll 队列为空,event loop 将检查 timer 队列是否为空,如果非空则进入下一轮 event loop
上面提到了如果在不同的 I/O 里,不能确定?setTimeout 和 setImmediate 的执行顺序,但如果 setTimeout 和 setImmediate 在一个 I/O 回调里,肯定是 setImmediate 先执行,因为在 poll 阶段检查到有?setImmediate() 任务,event loop 直接进入 check 阶段执行?setImmediate 回调const fs = require(‘fs‘);
fs.readFile(__filename, () => {
setTimeout(() => {
console.log(‘timeout‘);
}, 0);
setImmediate(() => {
console.log(‘immediate‘);
});
});
check
为什么 Promise.then 比 setTimeout 早一些
在 Node.js 环境下 microTask 会在每个阶段完成之间调用,也就是每个阶段执行最后都会执行一下 microTask 队列setImmediate(console.log, 1);
setTimeout(console.log, 1, 2);
/****************** microTask 分割线 ********************/
Promise.resolve(3).then(console.log); // microTask 分割线
/****************** 下次 event loop tick 分割线 ********************/
process.nextTick(console.log, 4);
/****************** 同步任务和异步任务的分割线 ********************/
console.log(5);
setImmediate VS process.nextTick
最开始的时候只有 process.nextTick 方法,没有 setImmediate 方法,通过上面的分析可以看出来任何时候调用?process.nextTick(),nextTick 会在 event loop 之前执行,直到 nextTick 队列被清空才会进入到下一 event loop,如果出现 process.nextTick 的递归调用程序没有被正确结束,那么 IO 的回调将没有机会被执行const fs = require(‘fs‘);
fs.readFile(‘a.txt‘, (err, data) => {
console.log(‘read file task done!‘);
});
let i = 0;
function test(){
if(i++
nextTick 1
nextTick 2
...
...
nextTick 999999
read file task done!
const EventEmitter = require(‘events‘);
const util = require(‘util‘);
function MyEmitter() {
EventEmitter.call(this);
this.emit(‘event‘);
}
util.inherits(MyEmitter, EventEmitter);
const myEmitter = new MyEmitter();
myEmitter.on(‘event‘, () => {
console.log(‘an event occurred!‘);
});
this.emit(‘event‘)
?会导致事件触发比事件回调函数绑定早,使用 process.nextTick 可以轻松实现预期效果const EventEmitter = require(‘events‘);
const util = require(‘util‘);
function MyEmitter() {
EventEmitter.call(this);
// use nextTick to emit the event once a handler is assigned
process.nextTick(() => {
this.emit(‘event‘);
});
}
util.inherits(MyEmitter, EventEmitter);
const myEmitter = new MyEmitter();
myEmitter.on(‘event‘, () => {
console.log(‘an event occurred!‘);
});
参考