• 技术文章 >web前端 >js教程

    了解Node.js中的定时器

    青灯夜游青灯夜游2020-12-03 18:13:10转载1882

    相关推荐:《node js教程

    timer 用于安排函数在未来某个时间点被调用,Node.js 中的定时器函数实现了与 Web 浏览器提供的定时器 API 类似的 API,但是使用了事件循环实现,Node.js 中有四个相关的方法

    前两个含义和 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、poll、check 阶段处理的

    timers

    Node.js 会在 timers 阶段检查是否有过期的 timer,如果存在则把回调放到 timer 队列中等待执行,Node.js 使用单线程,受限于主线程空闲情况和机器其它进程影响,并不能保证 timer 按照精确时间执行
    定时器主要有两种

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

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

    多次执行会发现打印的顺序不一样

    poll

    poll 阶段主要有两个任务

    当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 的调用机制确实也能为我们解决一些棘手的问题

    一个类继承了 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!');
    });

    更多编程相关知识,请访问:编程教学!!

    以上就是了解Node.js中的定时器的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:博客园,如有侵犯,请联系admin@php.cn删除
    专题推荐:Node.js 定时器
    上一篇:提示Node应用性能的5个技巧 下一篇:react可以引入css吗?
    VIP课程(WEB全栈开发)

    相关文章推荐

    • 【腾讯云】年中优惠,「专享618元」优惠券!• 如何使用Llama Logs显示和调试NodeJS错误?• 使用Node.js+Vue.js来构建文件压缩应用程序• 23个需要了解的十分有用的nodejs库(推荐)• Node.js中如何创建和提取zip文件?方法介绍• 详解Node.js中的事件
    1/1

    PHP中文网