一旦事件发生,事件发射器就会调用跟事件相关的监听器,并将相关数据作为参数传递给监听器。在前面http.request那个例子里,“data”事件回调函数接受一个data对象作为它第一个也是唯一的参数,而“end”不接受任何数据,这些参数作为API契约的一部分也是由API的作者主观定义的,这些回调函数的参数签名也会在每个事件发射器的API文档里有说明。
事件发射器虽然是个为所有类型事件服务的接口,不过“error”事件是Node里的一个特殊实现。Node里的大多数事件发射器都会在程序发生错误时产生“error”事件,如果程序没有监听某个事件发射器的 “error”事件,事件发射器将会注意到并在错误发生时向上抛出一个未捕获异常。
你可以在Node PERL里运行下面的代码来测试下效果,它模拟了一个能产生两种事件的事件发射器:
复制代码 代码如下:
var em = new (require('events').EventEmitter)();
em.emit('event1');
em.emit('error', new Error('My mistake'));
你将会看到下面的输出:
复制代码 代码如下:
var em = new (require('events').EventEmitter)();
undefined
> em.emit('event1');
false
> em.emit('error', new Error('My mistake'));
Error: My mistake
at repl:1:18
at REPLServer.eval (repl.js:80:21)
at repl.js:190:20
at REPLServer.eval (repl.js:87:5)
at Interface.<anonymous> (repl.js:182:12)
at Interface.emit (events.js:67:17)
at Interface._onLine (readline.js:162:10)
at Interface._line (readline.js:426:8)
at Interface._ttyWrite (readline.js:603:14)
at ReadStream.<anonymous> (readline.js:82:12)
>
代码第2行,随便发射了一个叫“event1”的事件,没有任何效果,但是当发射“error”事件时,错误被抛出到堆栈。如果程序不是运行在PERL命令行环境里,程序将会因为未捕获的异常而崩溃。
使用事件发射器API
任何实现了事件发射器模式的对象(比如TCP Socket,HTTP 请求等)都实现了下面的一组方法:
复制代码 代码如下:
.addListener和.on —— 为指定类型的事件添加事件监听器
.once —— 为指定类型的事件绑定一个仅执行一次的事件监听器
.removeEventListener —— 删除绑定到指定事件上的某个监听器
.removeAllEventListener —— 删除绑定到指定事件上的所有监听器
下面我们具体介绍它们。
使用.addListener()或.on()绑定回调函数
通过指定事件类型和回调函数,你可以注册当事件发生时被执行的操作。比如,文件读取数据流时如果有可用的数据块,就会发射一个“data”事件,下面代码展示如何通过传入一个回调函数来让程序告诉你发生了data事件。
复制代码 代码如下:
function receiveData(data) {
console.log("got data from file read stream: %j", data);
}
readStream.addListener(“data”, receiveData);
你也可以使用.on,它只是.addListener的简写方式,下面的代码和上面的是一样的:
复制代码 代码如下:
function receiveData(data) {
console.log("got data from file read stream: %j", data);
}
readStream.on(“data”, receiveData);
前面代码,使用事先定义的一个的命名函数作为回调函数,你也可以使用一个内联匿名函数来简化代码:
复制代码 代码如下:
readStream.on("data", function(data) {
console.log("got data from file read stream: %j", data);
});
前面说过,传递给回调函数的参数个数和签名依赖于具体的事件发射器对象和事件类型,它们并不是被标准化的,“data”事件可能传递的是一个数据缓冲对象,“error”事件传递一个错误对象,数据流的“end”事件不向事件监听器传递任何数据。
绑定多个事件监听器
事件发射器模式允许多个事件监听器监听同一个事件发射器的同一事件类型,比如:
复制代码 代码如下:
I have some data here.
I have some data here too.
事件发射器负责按监听器的注册顺序调用指定事件类型上绑定的所有监听器,也就是说:
1.当事件发生后事件监听器可能不会被立刻调用,也许会有其它事件监听器在它之前被调用。
2.异常被抛出到堆栈是不正常的行为,可能是因为代码里有bug,当事件被发射时,如果有一个事件监听器在被调用时抛出了异常,可能会导致一些事件监听器永远不会被调用。这种情况下,事件发射器会捕获到异常,也许还会处理它。
看下面这个例子:
复制代码 代码如下:
readStream.on("data", function(data) {
throw new Error("Something wrong has happened");
});
readStream.on("data", function(data) {
console.log('I have some data here too.');
});
因为第一个监听器抛出了异常,因此第二个监听器不会被调用。
用.removeListener()从事件发射器移除一个事件监听器
如果当你不再关心一个对象的某个事件时,你可以通过指定事件类型和回调函数来取消已注册的事件监听器,像这样:
复制代码 代码如下:
function receiveData(data) {
console.log("got data from file read stream: %j", data);
}
readStream.on("data", receiveData);
// ...
readStream.removeListener("data", receiveData);
这个例子里,最后一行把一个可能在将来被随时调用的事件监听器从事件发射器对象移除了。
为了删除监听器,你必须给回调函数命名,因为在添加和删除的时候需要回调函数的名字。
使用.once()让回调函数最多执行一次
如果你想监听一个最多执行一次的事件,或者只对某个事件发生的第一次感兴趣,可以用.once()函数:
复制代码 代码如下:
function receiveData(data) {
console.log("got data from file read stream: %j", data);
}
readStream.once("data", receiveData);
上面的代码,receiveData函数只会被调用一次。如果readStream对象发射了data事件,receiveData回调函数将会而且仅会被触发一次。
它其实只是个方便方法,因为很简单的就能实现它,像这样:
复制代码 代码如下:
var EventEmitter = require("events").EventEmitter;
EventEmitter.prototype.once = function(type, callback) {
var that = this;
this.on(type, function listener() {
that.removeListener(type, listener);
callback.apply(that, arguments);
});
};