1

There are places in my application where an object should emit an event and should delay execution untill all event handlers have completed work. The event handlers might be performing an asynchronous operation (e.g. writing to database).

Application.on("login", function(evt) {
    // Asynchronous operation (e.g. update info in database)
});

Application.trigger("login").then(function() {
    // Execute stuff after all (async) event handlers are done
});

I've looked around and haven't found any established design patterns for this. I've got an implementation I'm happy with, but I'm curious if anyone has solved this differently?

Stepan Riha
  • 1,736
  • 14
  • 12

1 Answers1

1

My solution is to add a promises[] property to the evt argument. Asynchronous event callbacks simply add a promise to this array and the triggerAsync function returns a promise that wait on all the promises to settle.

http://jsfiddle.net/nonplus/9aCC4/

Implementation of the triggerAsync method (using Backbone events and jQuery promises, but should work for other stacks, too):

// Trigger an event and return promise that waits on
// event handler promises
function triggerAsync(emitter, name, evt) {
    evt || (evt={});
    evt.promises = [];
    emitter.trigger(name, evt);
    return $.when.apply(null, evt.promises);
}

Usage of triggerAsync:

triggerAsync(obj, "myEvent").then(function() {
    // Code executed after event handlers are done
});

Implementation of an async event handler:

// Async handler doing an ajax request
obj.on("myEvent", function (evt) {
    evt.promises.push($.ajax(...));
});

// Async handler doing generic async operation
obj.on("myEvent", function (evt) {
    var dfd = $.Deferred();
    evt.promises.push(dfd.promise());

    // Async operation
    // Eventually calls dfd.resolve() or dfd.reject()
});
Stepan Riha
  • 1,736
  • 14
  • 12
  • Just curious, why use `evt = _.extend({promises: []}, evt);` instead of `evt.promises = [];`? – jfriend00 Apr 30 '14 at 16:32
  • This avoid modifying the passed-in evt object and also works if the evt parameter is undefined. – Stepan Riha Apr 30 '14 at 16:38
  • But yes, I could have initialized evt if undefined and then simply assigned the promises property. – Stepan Riha Apr 30 '14 at 16:39
  • It might be useful to more folks if your answer didn't depend on underscore just for that. Also, in your async event handler example, it might be good to show an ajax call where you can just use the promise that is already returned rather than creating your own deferred since that is probably the most common async operation and you're already requiring jQuery. – jfriend00 Apr 30 '14 at 16:42
  • I've simplified the example as suggested by @jfriend00 – Stepan Riha Apr 30 '14 at 16:50
  • 1
    I did a double take when I saw `evt || (evt={});` as I hadn't seen that pattern before. I'm used to seeing it like `evt = evt || {};`. – jfriend00 Apr 30 '14 at 17:19