Javascript eventemitter multiple events once

20,394

Solution 1

"Extend" the EventEmitter like this:

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);
    }); 
};

I created a gist here: https://gist.github.com/3627823 which includes an example (your example, with some logs)

[UPDATE] Following is an adaptation of my implementation of once that removes only the event that was called, as requested in the comments:

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));
    }); 
};

I found out that node.js already has a once method in the EventEmitter: check here the source code, but this is probably a recent addition.

Solution 2

How about something like this:

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);

You could write a wrapper around on and removeAllListeners to be able to pass in an array (e.g., loop over the array and call on or removeAllListeners for each element).

Solution 3

Use the first operator of 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>
Share:
20,394
Harry
Author by

Harry

Updated on March 03, 2021

Comments

  • Harry
    Harry about 3 years

    I'm using node's eventemitter though other event library suggestions are welcomed.

    I want to run a function once if several events are fired. Multiple events should be listened to, but all of them are removed if any one of the events fires. Hopefully this code sample demonstrates what I'm looking for.

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

    What is the cleanest way to handle this?

    Note: Need to remove bound functions individually because there will be other listeners bound.

  • Harry
    Harry over 11 years
    This actually breaks removeListener from working as it previously did. Maybe this answer could be expanded to fix that.
  • fableal
    fableal over 11 years
    Do you want the removeListener to remove only the event that was called, instead of all the events that are sent as the events argument? As soon as I get a idea of what you want, I'll update my answer.
  • Harry
    Harry over 11 years
    Just the event that was sent would be great.
  • CMCDragonkai
    CMCDragonkai about 10 years
    Does this work by waiting for both events to happen before calling the callback
  • CMCDragonkai
    CMCDragonkai about 10 years
    If you only emit one of them, callback still gets called.
  • CMCDragonkai
    CMCDragonkai about 10 years
    Is there one that only runs the callback if all events have occurred?