javascript eventemitter multiple events once

北战南征 提交于 2019-11-30 01:16:34

问题


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.


回答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.




回答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).




回答3:


Unfortunalely there is nothing similar you wish yet.

I look at EventEmitter2 but there is an issue i believe... (#66, #31)




回答4:


How about the following approach

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

when the CustomEventEmitter looks like this

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

}

The cascading of two event handlers produces the desired result. You are using an auxiliary EventEmitter but its details are well concealed within the CustomEventEmitter module, so that the usage of the module is pretty straightforward.




回答5:


Here's a generic helper function to handle this workflow. You'll need to pass a reference to the event emitter object, an array of the event names, and your event handler function. All attaching, detaching, and passing arguments through to your handler is taken care of.

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

And a usage example:

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



回答6:


I just ran into something similar and though to share it here. Could be that it also helps in your case?

I had a module that looked like this:

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

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

The funny thing is: This worked up until node.js 0.8.x but doesn't work anymore. The problem is this line:

Foobar.prototype = new EventEmitter();

With this line all the instances created share on and the same EventEmitter as non-scalar values in Javascript are always references. Obviously node.js changed some internal behavior regarding modules as this worked before.

Solution is using the util class which allows correct inheritance.

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

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

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

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

Hope this helps.




回答7:


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>


来源:https://stackoverflow.com/questions/12150540/javascript-eventemitter-multiple-events-once

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!