-
-
Save philipwalton/8402139 to your computer and use it in GitHub Desktop.
// host library code | |
program() | |
.initStuff() | |
.then(function() { | |
dispatcher.emit('beforeRenderPost') | |
}) | |
.then(function() { | |
dispatcher.emit('afterRenderPost') | |
}) | |
.then(function() { | |
dispatcher.emit('beforeWritePost') | |
}) | |
.then(function() { | |
dispatcher.emit('afterWritePost') | |
}) | |
.catch(errorHandler) | |
// now the user's code can simply be the following and the host logic | |
// will wait until their async function is finished before continuing | |
dispatcher.on('beforeRenderPost', doSomethingAsyncToPost) |
was sending messages over twitter but too hard to explain things under 140 chars.. so here we go.
I would not recommend following the API you proposed on the first tweet obj.on('foo').then(cb)
. The main issue that I see is that what you actually need is some sort of promise queue, exposing this sort of API would allow/incentive other parts of the app to listen for the completion of all the handlers, it would also only work once for each event (since I would expect it to always return same promise every time on('foo')
is called); I believe this logic should only be available to the internal module structure (the part of the app that triggers the listeners);
One simple way to implement it is to create some sort of queue and reuse a method like Q.all
:
function AsyncQueue() {
this._handlers = [];
}
AsyncQueue.prototype.execute = function(args){
if (!this._promise) {
var self = this;
this._promise = Q.all(this._handlers.map(function(handler){
return handler.apply(self, args);
}));
}
return this._promise;
};
AsyncQueue.prototype.listen = function(handler){
if (this._promise) {
throw new Error("can't add listener after event already happened");
}
this._handlers.push(handler);
};
and on your app code you would do:
program.onBeforeRenderPost = new AsyncQueue();
program.onAfterRenderPost = new AsyncQueue();
program.onBeforeWritePost = new AsyncQueue();
program.onAfterWritePost = new AsyncQueue();
program
.initStuff()
// beforeRenderPost could be replaced by `program.onBeforeRenderPost.execute.bind(program)`
.then(beforeRenderPost)
.then(renderPost)
.then(afterRenderPost)
.then(beforeWritePost)
.then(writePost)
.then(afterWritePost)
.catch(errorHandler);
function beforeRenderPost(){
return program.onBeforeRenderPost.execute();
}
// ... etc
and user would do:
program.onBeforeWritePost.listen(doMyAsyncOperation);
that should probably be enough for your use case.
PS: doing the same thing with callbacks would not be that hard either, would basically need to check against the expected Function.length
to define if you should wait for callback call or not.
need to remind that promises are usually used for actions that should only happen once, that's why you probably won't see an implementation of Events that has this kind of feature. - events usually/can happen more than once.
need to remind that promises are usually used for actions that should only happen once
True, but if the .emit()
function always returns a new promise instance, this wouldn't be an issue.
However, after giving it more thought I think I'm in agreement with you that events should be one-way communication. They have an established purpose and mixing in this behavior would be unnecessarily confusing.
I think it makes the most sense to create a new paradigm like the one you've outlined. I'll probably work on this for my project and hopefully release it once it's done.
would basically need to check against the expected Function.length to define if you should wait for callback call or not
I originally considered this. Like, if the callback function's .length
property is one greater than the emitter's arguments.length
property, assume async, but that won't work 100% of the time. Sometimes a callback will make use of the arguments object for ...rest
parameter type situations, so it gets a bit hairy.
Re: this blog post
Events/signals are a great way for host applications to allow third party plugin developers to build functionality on top of the application's core. They expose hook points allowing you to register a "listener" function to be executed at a particular point in time with the necessary arguments.
The problem with callbacks/events/signals (as opposed to promises) is that they don't notify you when the user's registered function has completed (assuming it's async).
For example, I'm working on a site generator that emits various events to plugin developers like:
beforeRenderPost
,afterRenderPost
,beforeWritePost
, andafterWritePost
. When all your code is synchronous this works just fine, but as you can see if the listener you register on afterRenderPost doesn't complete before the post is written to disk, there's nothing you can do about it, and the host application has no idea.I'm brainstorming a good solution to this problem, but aren't 100% happy with any of my options. Currently I'm leaning toward something like the above where the
emit()
method of event emitter instances returns a promise.I like the idea of this, but I'm afraid to adds unnecessary confusion to the existing understand of events. Thoughts?