2012-08-27 17 views
12

Ich benutze Node's eventemitter obwohl andere Event-Bibliothek Anregungen sind willkommen.Javascript eventemitter mehrere Ereignisse einmal

Ich möchte eine Funktion einmal ausführen, wenn mehrere Ereignisse ausgelöst werden. Mehrere Ereignisse sollten angehört werden, aber alle Ereignisse werden entfernt, wenn eines der Ereignisse ausgelöst wird. Hoffentlich zeigt dieses Codebeispiel, wonach ich suche.

var game = new eventEmitter(); 
game.once(['player:quit', 'player:disconnect'], function() { 
    endGame() 
}); 

Was ist der sauberste Weg, damit umzugehen?

Hinweis: Sie müssen gebundene Funktionen einzeln entfernen, da andere Listener gebunden sind.

Antwort

9

"Extend" die EventEmitter wie folgt aus:

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

EventEmitter.prototype.once = function(events, handler){ 
    // no events, get out! 
    if(! events) 
     return; 

    // Ugly, but helps getting the rest of the function 
    // short and simple to the eye ... I guess... 
    if(!(events instanceof Array)) 
     events = [events]; 

    var _this = this; 

    var cb = function(){ 
     events.forEach(function(e){  
      // This only removes the listener itself 
      // from all the events that are listening to it 
      // i.e., does not remove other listeners to the same event! 
      _this.removeListener(e, cb); 
     }); 

     // This will allow any args you put in xxx.emit('event', ...) to be sent 
     // to your handler 
     handler.apply(_this, Array.prototype.slice.call(arguments, 0)); 
    }; 

    events.forEach(function(e){ 
     _this.addListener(e, cb); 
    }); 
}; 

ich hier einen Kern erstellt: https://gist.github.com/3627823 enthält ein Beispiel (Ihr Beispiel mit einigen Protokollen)

[UPDATE] Fol Brüllen ist eine Anpassung meiner Implementierung von once, dass nur das Ereignis entfernt, die genannt wurde, wie in den Kommentaren angefordert:

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

EventEmitter.prototype.once = function(events, handler){ 
    // no events, get out! 
    if(! events) 
     return; 

    // Ugly, but helps getting the rest of the function 
    // short and simple to the eye ... I guess... 
    if(!(events instanceof Array)) 
     events = [events]; 

    var _this = this; 

    // A helper function that will generate a handler that 
    // removes itself when its called 
    var gen_cb = function(event_name){ 
     var cb = function(){ 
      _this.removeListener(event_name, cb); 
      // This will allow any args you put in 
      // xxx.emit('event', ...) to be sent 
      // to your handler 
      handler.apply(_this, Array.prototype.slice.call(arguments, 0)); 
     }; 
     return cb; 
    }; 


    events.forEach(function(e){ 
     _this.addListener(e, gen_cb(e)); 
    }); 
}; 

Ich fand heraus, dass node.js bereits eine once Methode im EventEmitter hat: Check here the source code, aber das ist wahrscheinlich eine neue Ergänzung.

+0

Dies bricht tatsächlich 'removeListener' von der Arbeit, wie es vorher war. Vielleicht könnte diese Antwort erweitert werden, um das zu beheben. – Harry

+0

Möchten Sie, dass der 'removeListener' nur das Ereignis löscht, das aufgerufen wurde, und nicht alle Ereignisse, die als 'events'-Argument gesendet werden? Sobald ich eine Vorstellung davon habe, was du willst, werde ich meine Antwort aktualisieren. – fableal

+0

Nur das Ereignis, das gesendet wurde, wäre großartig. – Harry

3

Wie wäre es etwa so:

var game = new EventEmitter(); 

var handler = function() { 
    game.removeAllListeners('player:quit'); 
    game.removeAllListeners('player:disconnect'); 
    endGame(); 
}; 

game.on('player:quit', handler); 
game.on('player:disconnect', handler); 

Sie einen Wrapper um on schreiben konnte und removeAllListeners der Lage sein, in einem Array zu übergeben (zB Schleife über das Array und rufen on oder removeAllListeners für jedes Element) .

0

Wie über die folgenden Ansatz

var myEmitter = new CustomEventEmitter; 
myEmitter.onceAny(['event1', 'event2', 'event3', 'event4'], function() { 
     endGame(); 
}); 

wenn die CustomEventEmitter wie diese

function CustomEventEmitter() {}; 

// inherit from EventEmitter 
CustomEventEmitter.prototype = new EventEmitter; 

CustomEventEmitter.prototype.onceAny = function(arrayEvents, callback) { 
    // generate unique string for the auxiliary event 
    var auxEvent = process.pid + '-' + (new Date()).getTime(); 

    // auxiliary event handler 
    var auxEmitter = new EventEmitter; 

    // handle the event emitted by the auxiliary emitter 
    auxEmitter.once(auxEvent, callback); 

    for (var i = 0; i < arrayEvents.length; i++) { 
     this.once(arrayEvents[i], function() { 
      auxEmitter.emit(auxEvent); 
     }); 
    } 

} 

Die Kaskadierung von zwei Event-Handler sieht erzeugt das gewünschte Ergebnis. Sie verwenden einen zusätzlichen EventEmitter, dessen Details jedoch im CustomEventEmitter-Modul gut verborgen sind, sodass die Verwendung des Moduls ziemlich einfach ist.

0

Hier ist eine generische Hilfsfunktion für diesen Workflow. Sie müssen einen Verweis auf das Ereignisemitterobjekt, ein Array der Ereignisnamen und Ihre Ereignishandlerfunktion übergeben. Alle Argumente zum Anhängen, Entfernen und Übergeben an Ihren Handler sind erledigt.

// listen to many, trigger and detach all on any 
function onMany(emitter, events, callback) { 
    function cb() { 
     callback.apply(emitter, arguments); 

     events.forEach(function(ev) { 
      emitter.removeListener(ev, cb); 
     }); 
    } 

    events.forEach(function(ev) { 
     emitter.on(ev, cb); 
    }); 
} 

Und ein Anwendungsbeispiel:

// Test usage 
var EventEmitter = require('events').EventEmitter; 
var game = new EventEmitter(); 

// event list of interest 
var events = ['player:quit', 'player:disconnect']; 

// end game handler 
function endGame() { 
    console.log('end the game!'); 
} 

// attach 
onMany(game, events, endGame); 

// test. we should log 'end the game' only once 
game.emit('player:disconnect'); 
game.emit('player:quit'); 
+0

Funktioniert dies, indem auf beide Ereignisse gewartet wird, bevor der Rückruf aufgerufen wird – CMCDragonkai

+0

Wenn Sie nur einen von ihnen ausgeben, wird der Rückruf immer noch aufgerufen. – CMCDragonkai

0

Ich lief in etwas ähnlich und obwohl es hier zu teilen. Könnte es sein, dass es auch in deinem Fall hilft?

hatte ich ein Modul, das so aussah:

require('events').EventEmitter; 
function Foobar = {}; 
Foobar.prototype = new EventEmitter(); 
Foobar.prototype.someMethod = function() { ... } 

module.exports.getNewFoobar = function() { 
    return new Foobar(); 
}; 

Das Komische ist: Das bis node.js 0.8.x gearbeitet, aber nicht mehr funktioniert. Das Problem ist, diese Zeile:

Foobar.prototype = new EventEmitter(); 

Mit dieser Linie alle die Freigabe erstellt Instanzen auf und die gleiche EventEmitter als nicht-skalare Werte in Javascript sind immer Referenzen. Offensichtlich hat node.js einige interne Verhaltensweisen in Bezug auf Module geändert, da dies zuvor funktioniert hat.

Lösung verwendet die Util-Klasse, die korrekte Vererbung ermöglicht.

require('events').EventEmitter; 
var util = require('util'); 

function Foobar = {}; 
util.inherits(Foobar, EventEmitter); 

Foobar.prototype.someMethod = function() { ... } 

module.exports.getNewFoobar = function() { 
    return new Foobar(); 
}; 

Hoffe das hilft.

0

Verwenden Sie den first Betreiber von Rx.Observable:

let game = new EventEmitter(); 
 
let events = ['player:quit', 'player:disconnect']; 
 

 
//let myObserver = Rx.Observable.fromEventPattern(handler => { 
 
// events.forEach(evt => game.on(evt, handler)); 
 
//}).first(); 
 

 
let myObserver = Rx.Observable.merge(...events.map(evt => Rx.Observable.fromEvent(game, evt))).first(); 
 

 
myObserver.subscribe(() => { 
 
    // clean up 
 
    console.log('Goodbye!!'); 
 
}); 
 

 
game.emit('player:quit'); // Goodbye!! 
 
game.emit('player:quit'); // No output 
 
game.emit('player:disconnect'); // No output
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.4.0/Rx.min.js"></script> 
 
<script src="https://cdnjs.cloudflare.com/ajax/libs/EventEmitter/5.1.0/EventEmitter.min.js"></script>