Skip to content

Instantly share code, notes, and snippets.

@nzakas
Created October 16, 2013 21:48
Show Gist options
  • Save nzakas/7015508 to your computer and use it in GitHub Desktop.
Save nzakas/7015508 to your computer and use it in GitHub Desktop.
Async event emitter

What I'm considering for an async event emitter is a situation like this:

  1. I want to be able to fire events before something happens and after something happens.
  2. Each of the event handlers may do an async process.
  3. I need to know after all of the "before" event handlers have completed before doing the actual action that I'm saying will happen.

A synchronous example would look like:

emitter.emit('beforewrite');
doWrite();
emitter.emit('afterwrite');

The challenge is that the event handlers may all be doing async operations, so I need to wait until all of them have completed before doing the write operation.

Thoughts?

@royriojas
Copy link

I don't know if this could work for you but I have solved a similar situation doing this

var args = {};
args.promises = [];

// all listeners should do at some point
// args.promises.push(deferred.promise());
emitter.emit('beforewrite', args);

$.when(args.promises).then(function () {
  doWrite();
  emitter.emit('afterwrite');
});

I used a jQuery implementation of deferreds in node. Hope this could give you some ideas

@xjamundx
Copy link

I'm sure someone has built an EventEmitter that has some sense of a lifecycle, but here's my stab at something like it in a few minutes. :)

AsyncEmitter.js

var async = require('async');

function AsyncEmitter() {
    this.listeners = [];
}

AsyncEmitter.prototype.on = function(event, fn) {
    this.listeners[event] = fn; // should allow multiple, but yeah...
};

AsyncEmitter.prototype.emit = function(event) {
    async.each(this.listeners(event + ':before'), this.emitOne.bind(this), this.afterBefore.bind(this));
};

AsyncEmitter.prototype.afterBefore = function() {
    async.each(this.listeners(event + ':now'), this.emitOne.bind(this), this.afterNow.bind(this));
};

AsyncEmitter.prototype.afterNow = function() {
    this.listeners(event + ':after').forEach(this.emitOne.bind(this));
};

AsyncEmitter.prototype.emitOne = function(event, done) {
    this.listeners[event](done);
};

Usage Example

var emitter = new AsyncEmitter();

function doWrite(done) {
  console.log("la la la");
  setTimeout(done, 150);
}

emitter.on('write:before', function(done) {
  setTimeout(done, 50);
});
emitter.on('write:now', doWrite);
emitter.on('write:after', function() {
  console.log('write complete');
});

// actually start the emitter
emitter.emit('write');

@nzakas
Copy link
Author

nzakas commented Oct 16, 2013

Thanks for the feedback - I was more wondering if there was a utility that already existed so I don't have to hack one myself. :)

@elmasse
Copy link

elmasse commented Oct 16, 2013

IMHO that is conflicting with the spirit of the events. In your example you are telling or "warning" others that you are about to do an operation, doWrite(), and then everyone that is listening to that will act upon that signal. I think that your implementation should have no knowledge at all about what others are doing when you fire an event, that is, from my point of view, a vital part of implementing events.

Having preventable events is a different discussion.

If you want to "wait" until all the processes end, maybe you should implement another mechanism explicitly to do so, maybe a few before/afterWrite methods that can execute a function you give them as a parameter. In that way, it makes explicit that you are going to do a bunch of task before/after X. But, I strongly believe that events is not what you are looking for here.

My 2 cents,
Max

@xjamundx
Copy link

@shesek
Copy link

shesek commented Oct 29, 2013

I wrote a library a while ago to do exactly this: https://github.com/shesek/emit-async

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment