在 Node.js 中监听所有发出的事件

Posted

技术标签:

【中文标题】在 Node.js 中监听所有发出的事件【英文标题】:Listen to All Emitted Events in Node.js 【发布时间】:2011-07-07 22:10:40 【问题描述】:

在 Node.js 中有什么方法可以监听 EventEmitter 对象发出的所有事件?

例如,你能做类似...的事情吗?

event_emitter.on('',function(event[, arg1][, arg2]...) 

我的想法是我想抓取服务器端EventEmitterJSON.stringify 吐出的所有事件数据,通过 websockets 连接发送,在客户端将它们作为事件进行重组,然后然后在客户端对事件采取行动。

【问题讨论】:

_events 属性似乎取决于在对象上定义的侦听器,因此它不执行问题所要求的操作。换句话说,如果定义了一个监听器 e.on("foo",...),那么 "foo" 就会出现在 e._events 中,即使 e 从未真正发出 "foo"。另一方面,e 可能会发出“bar”,如果不听,就不会出现在 e._events 中。特别是对于调试,最好有这样一个“通配符”的能力,一个形式为 e.on("*",...) 的监听器,但是这个功能似乎不可用。 【参考方案1】:

我知道这有点老了,但到底是什么,这是您可以采取的另一种解决方案。

您可以轻松地对要捕获所有事件的发射器的发射功能进行猴子修补:

function patchEmitter(emitter, websocket) 
  var oldEmit = emitter.emit;

  emitter.emit = function() 
      var emitArgs = arguments;
      // serialize arguments in some way.
      ...
      // send them through the websocket received as a parameter
      ...
      oldEmit.apply(emitter, arguments);
  

这是非常简单的代码,应该适用于任何发射器。

【讨论】:

将此方法与 mail-listener2 一起使用以避免订阅所有事件只是为了记录它们,这对于我的小任务来说更简单。 由substack使用。 我也更喜欢这种方法。正如@yzorg 所说,没有额外的部门并且可以很好地用于记录目的。谢谢。 建议改行 oldEmit.apply(emitter, arguments);返回 oldEmit.apply(emitter, arguments);【参考方案2】:

如前所述,此行为不在 node.js 核心中。但是你可以使用 hij1nx 的 EventEmitter2:

https://github.com/hij1nx/EventEmitter2

它不会破坏任何使用 EventEmitter 的现有代码,但增加了对命名空间和通配符的支持。例如:

server.on('foo.*', function(value1, value2) 
  console.log(this.event, value1, value2);
);

【讨论】:

它还有一个emitter.onAny(listener) 方法,它添加了一个监听器,当任何事件发出时都会被触发。 这对于在事件发射器中烘焙的 node.js 的任何现有内部使用都无济于事,尽管这是提问者正在寻找的。我认为***.com/a/18087021/455556 完美地回答了这个问题 @SalmanPK onAny 如何传递事件名称?因为我试过obj.onAny(function() console.log(arguments););,我只得到了没有名字的事件参数……【参考方案3】:

使用 ES6 类非常简单:

class Emitter extends require('events') 
    emit(type, ...args) 
        console.log(type + " emitted")
        super.emit(type, ...args)
    

【讨论】:

这可以通过其他人创建的现有 EventEmitter 实例来完成吗? 用这种方法你不能,但你可以修补instance.prototype.emit函数【参考方案4】:

请注意,上述所有解决方案都将涉及围绕 node.js EventEmitter 内部实现的某种黑客行为。

这个问题的正确答案是:默认的 EventEmitter 实现不支持,你需要修改它

如果您查看 EventEmitter 的 node.js 源代码,您可以看到使用事件类型作为键从哈希中检索侦听器,如果未找到该键,它将直接返回而无需任何进一步操作:

https://github.com/nodejs/node/blob/98819dfa5853d7c8355d70aa1aa7783677c391e5/lib/events.js#L176-L179

这就是为什么像 eventEmitter.on('*', ()=>...) 这样的东西默认不能工作的原因。

【讨论】:

【参考方案5】:

从 Node.js v6.0.0 开始,完全支持新的 class 语法和参数扩展运算符,因此通过简单的继承和方法覆盖来实现所需的功能非常安全且相当容易:

'use strict';
var EventEmitter = require('events');

class MyEmitter extends EventEmitter 
  emit(type, ...args) 
    super.emit('*', ...args);
    return super.emit(type, ...args) || super.emit('', ...args);
  

此实现依赖于 EventEmitter 的原始 emit 方法返回 true/false 的事实,具体取决于事件是否由某个侦听器处理。请注意,覆盖包含 return 语句,因此我们为其他消费者保留此行为。

这里的想法是使用星形事件 (*) 创建在每个事件上执行的处理程序(例如,用于记录目的)和空事件 ('') 作为默认或捕获所有处理程序,如果没有其他事件捕获该事件,则会执行。

我们确保首先调用星号 (*) 事件,因为在没有任何处理程序的 error 事件的情况下,结果实际上是抛出异常。更多详情,请查看implementation of the EventEmitter

例如:

var emitter = new MyEmitter();

emitter.on('foo', () => console.log('foo event triggered'));
emitter.on('*', () => console.log('star event triggered'));
emitter.on('', () => console.log('catch all event triggered'));

emitter.emit('foo');
    // Prints:
    //   star event triggered
    //   foo event triggered

emitter.emit('bar');
    // Prints:
    //   star event triggered
    //   catch all event triggered

最后,如果 EventEmitter 实例已经存在,但您想将该特定实例调整为新行为,则可以通过在运行时修补方法轻松完成,如下所示:

emitter.emit = MyEmitter.prototype.emit;

【讨论】:

这是一个聪明的主意。荣誉【参考方案6】:

这是基于 Martin 上面提供的答案。我对节点有点陌生,所以我需要自己找出他的答案。最后的方法,logAllEmitterEvents 是重要的一点。

var events = require('events');
var hungryAnimalEventEmitter = new events.EventEmitter();

function emitHungryAnimalEvents()

    hungryAnimalEventEmitter.emit("HungryCat");
    hungryAnimalEventEmitter.emit("HungryDog");
    hungryAnimalEventEmitter.emit("Fed");


var meow = function meow()

  console.log('meow meow meow');


hungryAnimalEventEmitter.on('HungryCat', meow);

logAllEmitterEvents(hungryAnimalEventEmitter);

emitHungryAnimalEvents();

function logAllEmitterEvents(eventEmitter)

    var emitToLog = eventEmitter.emit;

    eventEmitter.emit = function () 
        var event = arguments[0];
        console.log("event emitted: " + event);
        emitToLog.apply(eventEmitter, arguments);
    

【讨论】:

【参考方案7】:

我需要跟踪所有库中所有发出的事件,因此我使用了prototype

此示例使用Typescript signature,但如果您不喜欢这种废话,则可以将其删除。

在调用中,this 指的是正在发射的对象。在我的项目中跟踪所有唯一对象:发射非常容易。

  // For my example I use a `set` to track unique emits.
  const items = new Set()

  const originalEmit = EventEmitter.prototype.emit;
  EventEmitter.prototype.emit = function (event: String | Symbol, ...args: any[]): boolean 

    // Do what you want here
    const id = this.constructor.name + ":" + event;
    if (!items.has(id)) 
      items.add(id);
      console.log(id);
    

    // And then call the original
    return originalEmit.call(event, ...args);
  

您可以非常轻松地扩展它并根据事件名称或类名称进行过滤。

【讨论】:

【参考方案8】:

猴子补丁将 onAny 方法添加到 EventEmitter。

能够只监控一个问题的事件是很有用的。

var EventEmitter=require('events')
var origemit=EventEmitter.prototype.emit;
Object.assign( EventEmitter.prototype, 
  emit:function()
    if(this._onAnyListeners)
        this._onAnyListeners.forEach((listener)=>listener.apply(this,arguments))
    
    return origemit.apply(this,arguments)
  ,
  onAny:function(func)
    if(typeof func !== 'function')
      throw new Error('Invalid type');
    
    if(!this._onAnyListeners)this._onAnyListeners=[];
    this._onAnyListeners.push(func);
  ,
  removeOnAny:function(func)
    const index = this._onAnyListeners.indexOf(func);
    if(index === -1)
      return;
    
    this._onAnyListeners.splice(index,1);
  
);
// usage example
//gzip.onAny(function(a)console.log(a))

【讨论】:

【参考方案9】:

您可能想查看 node.js 的 RPC 模块。如果我没记错的话,Dnode RPC 模块有一个 chat server/client example 类似于你正在尝试做的事情。因此,您可以使用他们的模块或复制他们正在做的事情。

简而言之,该示例显示了一个服务器,该服务器在连接时为来自已连接客户端的所有服务器事件创建侦听器。它通过简单地遍历存储的事件名称列表来实现这一点。

var evNames = [ 'joined', 'said', 'parted' ];

con.on('ready', function () 
    evNames.forEach(function (name) 
        emitter.on(name, client[name]);
    );
    emitter.emit('joined', client.name);
);

这段代码很聪明,因为它会在事件发出时自动在与该事件关联的客户端上调用远程过程调用。

【讨论】:

【参考方案10】:

今天遇到了同样的问题,给个解决办法:

Object.create(Object.assign(,EventEmitter.prototype, 
  _onAnyListeners:[],
  emit:function(...args)
    //Emit event on every other server

    if(this._fireOnAny && typeof this._fireOnAny === 'function')
      this._fireOnAny.apply(this,args)
    

    EventEmitter.prototype.emit.apply(this,args)
  ,
  _fireOnAny:function(...args)
    this._onAnyListeners.forEach((listener)=>listener.apply(this,args))
  ,
  onAny:function(func)
    if(typeof func !== 'function')
      throw new Error('Invalid type');
    
    this._onAnyListeners.push(func);
  ,
  removeOnAny:function(func)
    const index = this._onAnyListeners.indexOf(func);
    if(index === -1)
      return;
    
    this._onAnyListeners.splice(index,1);
  
));

【讨论】:

如果您不能自己创建 EventEmitter,则必须直接分配给原型。【参考方案11】:

这是一个受 Martin 的回答 (https://***.com/a/18087021/1264797) 启发的调试工具。我刚刚使用它通过将所有事件记录到控制台来找出一组流中出了什么问题。效果很好。正如 Martin 说明的那样,OP 可以通过将 console.log() 调用替换为 websocket 发送器来使用它。

function debug_emitter(emitter, name) 
    var orig_emit = emitter.emit;
    emitter.emit = function() 
        var emitArgs = arguments;
        console.log("emitter " + name + " " + util.inspect(emitArgs));
        orig_emit.apply(emitter, arguments);
    

【讨论】:

你好像不见了var util = require('util');【参考方案12】:

您还可以使用另一个事件发射器实现,例如https://github.com/ozantunca/DispatcherJS。实现如下:

dispatcher.on('*', function () );

DispatcherJS 还支持命名空间甚至依赖项来确定首先调用哪些回调。

【讨论】:

以上是关于在 Node.js 中监听所有发出的事件的主要内容,如果未能解决你的问题,请参考以下文章

Node.js自定义对象事件监听与发射

Node.js - 如何从 app.js 外部发出 socket.io 事件?

Websocket 内存泄漏 node.js。事件发射器?

如果使用 Node.js 在 Socket.io 中的应用程序中登录功能,则为特定用户发出事件

Node.js EventEmitter

Node.js EventEmitter