百分百源码网-让建站变得如此简单! 登录 注册 签到领金币!

主页 | 如何升级VIP | TAG标签

当前位置: 主页>网站教程>JS教程> 理解Node.js中的按时器
分享文章到:

理解Node.js中的按时器

发布时间:12/01 来源:未知 浏览: 关键词:

相关引荐:《node js教程》

timer 用于安排函数在将来某个时间点被调取,Node.js 中的按时器函数实现了与 Web 阅读器供给的按时器 API 相似的 API,但是使用了事件轮回实现,Node.js 中有四个相关的办法

  • setTimeout(callback, delay[, ...args])

  • setInterval(callback[, ...args])

  • setImmediate(callback[, ...args])

  • process.nextTick(callback[, ...args])

前两个含义和 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);

会打印 5 4 3 2 1 或者 5 4 3 1 2

同步 & 异步

第五行是同步施行,其它都是异步的

setImmediate(console.log, 1);
setTimeout(console.log, 1, 2);
Promise.resolve(3).then(console.log);
process.nextTick(console.log, 4);
/****************** 同步任务和异步任务的分割线 ********************/
console.log(5);

所以先打印 5,这个很好懂得,剩下的都是异步操纵,Node.js 依照什么次序施行呢?

event loop

Node.js 启动后会初始化事件轮询,历程中大概处置异步调取、按时器调度和 process.nextTick(),然后开端处置event loop。官网中有这样一张图用来介绍 event loop 操纵次序

┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
   └───────────────────────────┘

event loop 的每个阶段都有一个任务队列,当 event loop 进入给定的阶段时,将施行该阶段的任务队列,直到队列清空或施行的回调到达系统上限后,才会转入下一个阶段,当所有阶段被次序施行一次后,称 event loop 完成了一个 tick

异步操纵都被放到了下一个 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、setInterval 回调

  • pending callbacks:施行 I/O(文件、网络等) 回调

  • idle, prepare:仅供系统内部调取

  • poll:猎取新的 I/O 事件,施行相关回调,在恰当前提下把堵塞 node

  • check:setImmediate 回调在此阶段施行

  • close callbacks:施行 socket 等的 close 事件回调

日常开发中绝大部分异步任务都是在 timers、poll、check 阶段处置的

timers

Node.js 会在 timers 阶段检查可否有过期的 timer,假如存在则把回调放到 timer 队列中等候施行,Node.js 使用单线程,受限于主线程余暇状况和机器其它进程影响,并不克不及包管 timer 依照准确时间施行
按时器主要有两种

  • Immediate

  • Timeout

Immediate 类型的计时器回调会在 check 阶段被调取,Timeout 计时器会在设定的时间过期后尽快的调取回调,但

setTimeout(() => {
  console.log('timeout');
}, 0);

setImmediate(() => {
  console.log('immediate');
});

屡次施行会发明打印的次序不一样

poll

poll 阶段主要有两个任务

  • 运算应当堵塞和轮询 I/O 的时间

  • 然后,处置 poll 队列里的事件

当event loop进入 poll 阶段且没有被调度的计时器时

  • 假如 poll 队列不是空的 ,event loop 将轮回拜访回调队列并同步施行,直到队列已用尽或者到达了系统或到达最大回调数
  • 假如 poll 队列是空的
    • 假如有 setImmediate() 任务,event loop 会在完毕 poll 阶段后进入 check 阶段
    • 假如没有 setImmediate()任务,event loop 堵塞在 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

在该阶段施行 setImmediate 回调

为什么 Promise.then 比 setTimeout 早一些

前端同学必定都据说过 micoTask 和 macroTask,Promise.then 属于 microTask,在阅读器环境下 microTask 任务会在每个 macroTask 施行最末端调取

在 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

setImmediate 听起来是马上施行,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++ < 999999) {
  	console.log(`process.nextTick ${i}`);
    process.nextTick(test);
  }
}
test();

施行程序将返回

nextTick 1
nextTick 2
...
...
nextTick 999999
read file task done!

于是乎需要一个不这么 bug 的调取,setImmediate 办法显现了,比力令人费解的是在 process.nextTick 起错名字的状况下,setImmediate 也用了一个错误的名字以示区分。。。

那么是不是编程中应当杜绝使用 process.nextTick 呢?官方引荐大部分时候应当使用 setImmediate,同时对 process.nextTick 的最大调取堆栈做了限制,但 process.nextTick 的调取机制确实也能为我们解决一些棘手的问题

  • 同意会员在 even tloop 开端此前 处置非常、施行清算任务

  • 同意回调在调取栈 unwind 之后,下次 event loop 开端此前施行

一个类继承了 EventEmitter,并且想在实例化的时候触发一个事件

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!');
});
打赏

打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

百分百源码网 建议打赏1~10元,土豪随意,感谢您的阅读!

共有151人阅读,期待你的评论!发表评论
昵称: 网址: 验证码: 点击我更换图片
最新评论

本文标签

广告赞助

能出一分力是一分吧!

订阅获得更多模板

本文标签

广告赞助

订阅获得更多模板