• Node 中的 Events


    Node 中的 Events

    Node 的 Events 模块只定义了一个类,就是 EventEmitter(以下简称 Event ),这个类在很多 Node 本身以及第三方模块中大量使用,通常是用作基类被继承。

    在 Node 中,事件的应用遍及代码的每一个角落。

    1. 事件和监听

    Node 程序中的对象会产生一系列的事件,它们被称为事件触发器(event emitter),例如一个 HTTP Server 会在每次有新连接时触发一个事件,一个 Readable Stream 会在文件打开时触发一个事件等。

    所有能触发事件的对象都是 EventEmitter 类的实例。EventEmitter 定义了 on 方法,该方法的声明如下:

    emitter.on(eventName, listener)
    eventName <String> | <Symbol> The name of the event.
    listener <Function> The callback function
    
    • 1
    • 2
    • 3

    on 方法接受两个参数:需要监听的事件的名称,当事件触发时需要调用的函数。因为 EventEmitter 是接口,从 EventEmitter 继承的类需要使用 new 关键字来构造。

    触发事件监听器很简单,只要调用 EventEmitter实例的 emit 方法就行了。需要注意的是,这些事件是针对某个实例的,不存在全局的事件。当你调用 on 方法的时候,需要绑定在特定的基于 EventEmitter 的对象上。EventEmitter 类不同的实例之间也不会共享事件。

    下面是一个事件注册和触发事件的例子。

    const eventEmitter = require('events');
    const myEmitter = new eventEmitter();
    myEmitter.on('begin', () => {
      console.log('begin');
    });
    
    myEmitter.emit('begin');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    上面的代码中,首先初始化了一个 EventEmitter 实例,然后注册了一个名为 begin 的事件,之后调用 emit 方法触发了这一事件。

    用户可以注册多个同名的事件,在上面的例子中,如果注册两个名为 begin 的事件,那么它们都会被触发。

    如果想获取当前的 emitter 一共注册了哪些事件,可以使用 eventNames 方法。

    console.log(myEmitter.eventNames());
    
    • 1

    该方法会输出包括全部事件名称的数组。就算注册了两个同名的 event,输出结果也只有一个,说明该方法的结果集并不包含重复结果。

    2. 处理 error 事件

    由于 Node 代码运行在单线程环境中,那么运行时出现的任何错误都有可能导致整个进程退出。利用事件机制可以实现简单的错误处理功能。

    当 Node 程序出现错误的时候,通常会触发一个错误事件,如果代码中没有注册相应的处理方法,会导致 Node 进程崩溃退出。例如:

    myEmitter.emit("error", new Error("crash!"));
    
    • 1

    上面的代码主动抛出了一个 emor,相当于:

    throw new Error ("crash");
    
    • 1

    如果我们不想因为抛出一个 error 而使进程退出,那么可以让 uncaughtException 事件作为最后一道防线来捕获异常。

    const eventEmitter = require('events');
    const myEmitter = new eventEmitter();
    process.on('uncaughtException', () => {
      console.log('got error');
    });
    throw new Error('Error occurred');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这种错误处理的方式虽然可以捕获异常,避免了进程的退出,但不值得提倡。

    关于其常见的方法如下:

    • emitter.addListener/on(eventName, listener) :添加类型为 eventName 的监听事件到事件数组尾部
    • emitter.prependListener(eventName, listener):添加类型为 eventName 的监听事件到事件数组头部
    • emitter.emit(eventName[, ...args]):触发类型为 eventName 的监听事件
    • emitter.removeListener/off(eventName, listener):移除类型为 eventName 的监听事件
    • emitter.once(eventName, listener):添加类型为 eventName 的监听事件,以后只能执行一次并删除
    • emitter.removeAllListeners([eventName]): 移除全部类型为 eventName 的监听事件

    3. 继承 Events 模块

    在实际的开发中,通常不会直接使用 Event 模块来进行事件处理,而是选择将其作为基类进行继承的方式来使用 Event,在 Node 的内部实现中,凡是提供了事件机制的模块,都会在内部继承 Event 模块。

    4. 手写 EventEmitter

    下面我们来看看如何手写一个 EventEmitter

    class EventEmitter {
      constructor() {
        this.events = {};
      }
    
      on(type, handler) {
        if (!this.events[type]) {
          this.events[type] = [];
        }
        this.events[type].push(handler);
      }
    
      addListener(type, handler) {
        this.on(type, handler)
      }
    
      prependListener(type, handler) {
        if (!this.events[type]) {
          this.events[type] = [];
        }
        this.events[type].unshift(handler);
      }
    
      removeListener(type, handler) {
        if (!this.events[type]) {
          return;
        }
        this.events[type] = this.events[type].filter(item => item !== handler);
      }
    
      off(type, handler) {
        this.removeListener(type, handler)
      }
    
      emit(type, ...args) {
        this.events[type].forEach((item) => {
          Reflect.apply(item, this, args);
        });
      }
    
      once(type, handler) {
        this.on(type, this._onceWrap(type, handler, this));
      }
    
      _onceWrap(type, handler, target) {
        const state = {
          fired: false,
          handler,
          type,
          target
        };
        const wrapFn = this._onceWrapper.bind(state);
        state.wrapFn = wrapFn;
        return wrapFn;
      }
    
      _onceWrapper(...args) {
        if (!this.fired) {
          this.fired = true;
          Reflect.apply(this.handler, this.target, args);
          this.target.off(this.type, this.wrapFn);
        }
      }
    }
    
    • 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
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
  • 相关阅读:
    【无标题】
    rtp流广播吸顶喇叭网络有源吸顶喇叭
    网络系统管理 - Server01配置
    磁珠元器件:微小却强大的科技奇迹 | 百能云芯
    词嵌入数据集的预处理--Word2Vec实现(一)
    python进阶(28)import导入机制原理
    Opencv项目实战:11 使用Opencv高亮显示文本检测
    【Java基础面试三十四】、接口中可以有构造函数吗?
    JNDI注入的理解、JDK给出的修复
    插入排序—直接插入排序和希尔排序
  • 原文地址:https://blog.csdn.net/p1967914901/article/details/126179065