Ryan的Koa系列博客4.依赖库:events概述

前言

events库是node的内置库的内容,简单点说就是node的事件发布器。(订阅\发布)
node的事件发布器可以为所有的引擎提供事件发布功能。

安装

npm install events

依赖

var EventEmitter = require('events').EventEmitter

node的事件机制

大多数 Node.js 核心 API 都是采用惯用的异步事件驱动架构,其中某些类型的对象(一般称为触发器emitters)会周期性地发送(触发)被命名好的相关事件来调用被称为监听器的函数对象(一般称为触发器listeners)。

例如:
每次对等连接(a peer connects to it)时或者新的连接时,一个服务器(net.Server)对象都会发出(触发)一个事件;
当文件打开时,一个文件流(fs.ReadStream)对象都会发出(触发)一个事件;
只要数据可用被读取,一个流(stream )对象都会发出(触发)一个事件。

所有能发送(触发)事件的对象都是EventEmitter类的实例。这些对象都会暴露一个名为eventEmitter.on()的函数,并允许一个或者多个函数通过这个对象与一些有名字的事件关联到一起。事件名通常都是驼峰形式的字符串,但是,也可以用任何有效的js属性键作为事件名。

???
当EventEmitter对象发出一个事件时,所有附加在特定事件上的函数都被同步地调用,被调用的监听器返回的任何值将被忽略和丢弃。(什么叫做:被调用的监听器返回的任何值将被忽略和丢弃?是不是正是因为如此,才需要使用高级函数呢?)
???

以下例子展示了一个只有单个监听器(listener)的 EventEmitter 实例。 eventEmitter.on() 方法用于注册监听器(listener),eventEmitter.emit() 方法用于触发事件。下面我们看一个例子:

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();
console.log('开始注册事件');
myEmitter.on('event',() => { console.log('发生了一个事件'); }); console.log('开始触发事件事件'); myEmitter.emit('event'); 

执行结果:

给监听器(listener)传入参数与this的使用方法

在es5的语法下,eventEmitter.emit() 方法允许将任意参数集合传给监听器函数。 当一个普通的监听器函数被 EventEmitter 调用时,标准的 this 关键词会被设置指向监听器所附加的 EventEmitter。也就是说,this在监听器关联的事件中就代表EventEmitter对象。

我们来看一个例子:

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();
myEmitter.on('event',function(a,b) {
    console.log(a,b,this);
    // Prints:
    // a b MyEmitter {
    // domain: null,
    // _events: { event: [Function] },
    // _eventsCount: 1,
    // _maxListeners: undefined }
});
myEmitter.emit('event','a','b');

执行结果:

也可以使用 ES6 的箭头函数作为监听器。但是这样 this 关键词就不再指向 EventEmitter 实例,我们来看es6的例子:

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();
myEmitter.on('event',(a,b) => {
    console.log(a,this);
// Prints: a b {}
});
myEmitter.emit('event','b');

同步和异步

EventListener 会按照监听器注册的顺序同步地调用所有监听器。 所以需要确保事件的正确排序且避免竞争条件或逻辑错误。 监听器函数可以使用 setImmediate() 或 process.nextTick() 方法切换为异步操作模式。

来我们看个例子:

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();
myEmitter.on('event',b) => {
    setImmediate(() => {
        console.log('这个是异步发生的A');
});
console.log('这个是监听器的B');
// Prints:这个是监听器的B
//这个是异步发生的A
});
myEmitter.emit('event','b');

让事件使用一次之后被销毁

使用 eventEmitter.on() 方法注册监听器时,监听器会在每次触发命名事件时都被调用。

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();
var m = 0;
myEmitter.on('event',() => {
    console.log(++m);
});
myEmitter.emit('event');
// 打印: 1
myEmitter.emit('event');
// 打印: 2

如果,不想让某个被注册的事件被调用多次,可以使用eventEmitter.once() 。使用 eventEmitter.once() 可以使被注册的事件,只能被监听器调用一次。 当事件被触发后,该事件的监听器会先被注销,然后再调用事件中的功能函数。我们来看下边的例子:

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();
var m = 0;
myEmitter.once('event',() => {
    console.log(++m);
});
myEmitter.emit('event');
// 打印: 1
myEmitter.emit('event');
// 忽略

报错事件

When an error occurs within an EventEmitter instance,the typical action is for an ‘error’ event to be emitted. These are treated as special cases within Node.js.

If an EventEmitter does not have at least one listener registered for the ‘error’ event,and an ‘error’ event is emitted,the error is thrown,a stack trace is printed,and the Node.js process exits.

当 EventEmitter 实例中发生错误时,典型做法应该是去注册一个 ‘error’ 事件,并触发这个事件。但是,这在 Node.js 中是个特例,或者说不应该被推荐的。因为,如果 EventEmitter 没有为 ‘error’ 事件注册至少一个监听器,则当 ‘error’ 事件触发时,会抛出错误,打印堆栈信息,然后退出 Node.js 进程。我们来看下边的一个例子:

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();
myEmitter.emit('error',new Error('whoops!'));
// Throws and crashes Node.js

如果又不想为事件注册监听器,又想防止 Node.js 进程崩溃,可以在 process 对象的 uncaughtException 事件上注册监听器。(node核心中提供了domain 模块,但是,该模块官方已经准备废弃了,我用过这个domain 模块,该模块的问题是过于占用内存,会影响整个系统的性能。不知道该模块的底层是什么样的结构,反正,是一个已经准备废弃的模块了,因此,也不需要去查看了),我们来看这样一个例子:

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();

process.on('uncaughtException',(err) => {
    console.log('有错误');
});

myEmitter.emit('error',new Error('whoops!'));
// 打印: 有错误

当然,作为最佳实践,应该始终为 ‘error’ 事件注册监听器。

例子如下:

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();
myEmitter.on('error',(err) => {
    console.log('有错误');
});
myEmitter.emit('error',new Error('whoops!'));
// 打印: 有错误

后记

本文只是依赖库events的概述。通过本文,我们知道了最主要的事件类EventEmitter,也知道了其他的事件类都是EventEmitter的实例,同时,我们还学习了注册、监听、触发、错误处理等多种事件的操作。node的异步事件是它的特殊之一,因此,学好node事件,是学好node的基础。本文篇幅有限,因此,关于EventEmitter类的深入解析将会在后续的文章中进行讲解。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结