Прослухайте всі випущені події в Node.js


78

У Node.js є спосіб прослуховувати всі події, випущені об’єктом EventEmitter?

наприклад, чи можете ви зробити щось на зразок ...

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

Ідея полягає в тому, що я хочу захопити всі події, виплеснені стороною сервера EventEmitter, JSON.stringifyдані про події, надіслати їх через з'єднання веб-сокетів, реформувати їх на стороні клієнта як подію, а потім діяти щодо події на стороні клієнта .


Атрибут _events, здається, залежить від слухачів, які визначені для об'єкта, тому він не робить того, що задає питання. Іншими словами, якщо хтось визначає слухача e.on ("foo", ...), тоді "foo" відображається в e._events, навіть якщо e насправді ніколи не видає "foo". З іншого боку, e може видавати "bar", який, якщо його не прослухати, не відображатиметься у e._events. Для налагодження, зокрема, було б непогано мати таку можливість "підстановки", прослуховувач форми e.on ("*", ...), але ця функція, схоже, недоступна.
teu

Відповіді:


42

Як вже згадувалося, така поведінка не в ядрі node.js. Але ви можете використовувати EventEmitter2 hij1nx:

https://github.com/hij1nx/EventEmitter2

Він не порушить жодного існуючого коду за допомогою EventEmitter, але додає підтримку просторів імен та символів підстановки. Наприклад:

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

12
Він також має emitter.onAny(listener)метод, який додає слухача, який буде запущений під час випуску будь-якої події.
Salman von Abbas

4
Це не допоможе з будь-яким наявним внутрішнім використанням node.js, випеченого у випромінювачі подій, хоча саме це шукає запитувач. Я думаю , що stackoverflow.com/a/18087021/455556 відповідає на питання відмінно
Джон Culviner

2
@SalmanPK Як передається onAnyімена подій? Тому що я намагався, obj.onAny(function() {console.log(arguments);});і я отримав лише параметри події без імен ...
Томаш Зато - Відновити Моніку

78

Я знаю, що це трохи давно, але, який біс, ось ще одне рішення, яке ви можете взяти.

Ви можете легко виправити мавпою функцію випромінювання випромінювача, який ви хочете вловити всі події:

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, щоб уникнути необхідності підписуватися на всі події лише для їх реєстрації, це простіше для мого крихітного завдання.
yzorg

1
Як використовується substack .
timruffles

Я також віддав перевагу цьому методу. Немає зайвих служб і працює досить добре для ведення журналу, як сказав @yzorg. Дякую.
Сейрія

1
запропонувати змінити рядок oldEmit.apply (випромінювач, аргументи); повернути oldEmit.apply (випромінювач, аргументи);
Офіген

19

З класами ES6 це дуже просто:

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

3
чи можна це зробити в існуючому екземплярі EventEmitter, який створив хтось інший?
Євген

при такому підході ви не можете, але ви можете виправити instance.prototype.emitфункцію
правдомил

9

Майте на увазі, що всі описані вище рішення включатимуть певний злом навколо внутрішньої реалізації node.js EventEmitter.

Правильною відповіддю на це запитання було б: реалізація EventEmitter за замовчуванням не підтримує цього, вам потрібно її зламати .

Якщо ви подивитесь на вихідний код node.js для EventEmitter, ви побачите, що слухачі отримуються з хешу, використовуючи тип події як ключ, і він просто повернеться без будь-яких подальших дій, якщо ключа не знайдено:

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

Ось чому щось на зразок eventEmitter.on('*', ()=>...)не може працювати за замовчуванням.


6

Оскільки 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);
  }
}

Ця реалізація спирається на той факт, що вихідний emitметод EventEmitterповертається true/ falseзалежно від того, обробляв подію якийсь слухач чи ні. Зверніть увагу, що заміна включає returnтвердження, тому ми зберігаємо таку поведінку для інших споживачів.

Тут ідея полягає у використанні події зірки ( *) для створення обробників, які виконуються для кожної окремої події (скажімо, для реєстрації), а порожньої події ( '') - за замовчуванням або ловлять усі обробники, які виконуються, якщо ніщо інше не вловлює це подія.

Ми обов’язково викликаємо *спочатку подію star ( ), оскільки у випадку errorподій без будь-яких обробників результат насправді є винятком. Щоб отримати докладнішу інформацію, погляньте на реалізацію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;

1

Це базується на відповіді, яку Мартін дав вище. Я трохи новачок, тому мені потрібно було виробити його відповідь для себе. Метод наприкінці 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);
    }
}

1

Мені потрібно було простежити всі випущені події у всіх бібліотеках, тому я скористався prototype.

У цьому прикладі використовується a 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);
  }

Ви можете дуже легко розширити це і відфільтрувати на основі назви події або назви класу.


0

Ви можете розглянути модулі RPC для node.js. Якщо я не помиляюсь, модуль RPC Dnode має приклад сервера чату / клієнта подібний до того, що ви намагаєтесь зробити. Таким чином, ви можете або скористатися їх модулем, або скопіювати те, що вони роблять.

Коротко в прикладі показано сервер, який при підключенні створює слухачі для всіх подій сервера від підключеного клієнта. Він робить це, просто перебираючи збережений список назв подій.

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

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

Цей код розумний, оскільки він автоматично викликає віддалений виклик процедури для клієнта, пов'язаного з подією, коли подія випускається.


0

Натрапив на ту саму проблему сьогодні, ось рішення:

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, вам доведеться призначити безпосередньо прототип.
Еладіан,

0

патч мавпи додає метод 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)})

-1

Ось інструмент налагодження, натхненний відповіддю Мартіна ( https://stackoverflow.com/a/18087021/1264797 ). Я щойно використав це, щоб з’ясувати, що пішло не так у наборі потоків, записавши всі їх події на консоль. Чудово працює. Як ілюструє Мартін, OP міг використовувати його, замінивши виклик console.log () на відправника веб-сокета.

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');
Павел П

-2

Ви також можете використовувати іншу реалізацію випромінювача подій, наприклад https://github.com/ozantunca/DispatcherJS . Реалізація буде виглядати так:

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

DispatcherJS також підтримує простори імен і навіть залежності, щоб визначити, які зворотні виклики будуть викликані першими.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.