I'm fully aware that Promises and eventing are both ways to do asynchronous programming, and you can choose either one for a given project. However, in practice, the code base I'm working with is large and we use many 3rd party libraries that handles asynchronicity differently so mixing the two approaches together are unavoidable (and often with disastrous effect).
Too many times I've seen people fire an event and then do more stuff after it, while expecting the event handlers to have fully finished. Unfortunately some event handlers will start a chain of Promises but there is no way for the event source to know when that chain is resolved.
In theory, the proper way to do async programming with pure eventing alone would have been for the event handler to raise an "event_x_completed" event but that assumes that there is always exactly one event handler for the original "event_x" (unless you want the code to get ugly. See example at the bottom). In UI programming, we often should not care how many listeners there are. E.g. one user action could raise an event to trigger any number of UI components to refresh themselves.
Also, depending how it is done, it could create a tight coupling between the event source and the event listeners, which invalidates a big strength to use event in the first place. My project has a complicated UI so an event source can't/shouldn't care how many handlers are listening to an event.
I think it would be great to have an eventing system that tests whether each handler returns a Promise (or then-able object), and fire("event_x") should return Promise.all(all_promises_from_handlers) to the event source.
I've looked around (maybe not hard enough) and I don't see anyone talking about a Promise-aware eventing system. Do people have the same problem and how is it handled?
Edit for concrete example:
The originator of "SAVE_PRESSED" doesn't care how many handlers there are, it only cares that they are all completed. I think it would be immensely useful to be able to do this:
function saveClicked() {
saveButton.disable();
EventManager.raise("SAVE_PRESSED").then(function() {
saveButton.enable();
});
}
With pure event, of course we could loop through all the listeners and do this instead:
function saveClicked() {
saveButton.disable();
var listeners = EventManager.getListeners("SAVE_PRESSED"); // array
EventManager.raise("SAVE_PRESSED");
EventManager.listen("SAVE_COMPLETED", function(listener_id) {
var index = listeners.indexOf(listener_id);
listeners = listeners.splice(index, 1);
if (listeners.length === 0) {
saveButton.enable();
// AND -- unregister this function as a handler for SAVE_COMPLETED
}
});
}
What I don't like about this:
- The code is longer and it's not even complete and doesn't have proper error handling.
- This depends on the listeners raising a "*_COMPLETED" event.
- If this is not always true or required, then each listener needs to indicate whether they are async (will raise a COMPLETED event) or not. This is additional configuration to maintain.
- And instead of EventManager.getListeners("SAVE_PRESSED"), we call EventManager.getAsyncListeners("SAVE_PRESSED");
- This code / functionality should be provided by the EventManager
- If it is moved into the EventManager, then "*_COMPLETED" events are essentially very special.
In a promise-aware event system, the handlers can tell the system that they are async by simply returning a then-able object.