Currently, I’m working on an application that uses a lot of global events which I lovingly call the “invoke global search in your editor to find anything” architecture. Long story short, there are a lot of pieces of code loaded dynamically and I wanted a way to those pieces to hook together fairly easily.
Yes, I know there are better ways to do this… this is not a discussion about that.
Anyway, I have a global events object in it’s own module that looks something like:
1 2 3 4 |
var events = require('events'); exports = module.exports = new events.EventEmitter(); |
I found myself using a common pattern of wanting to have an event handler call a callback when it was done processing. This way things were nice and asynchronous.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
var events = require('./events'); // My events module // Add some handler events.on('cool.event', function(data, fn) { // Do some processing on data here // Now call the callback if it exists if (fn) // Use the standard err, data node.js callback format fn(undefined, data); }); // Use said handler events.emit('cool.event', {data: 'awesome'}, function(err, data) { if (err) { // Handle an error if one should happen to show up } else { // Do something with data here } }); |
Easy enough. This pattern worked great and I used it throughout my app.
However, I ran into an issue. I had an array of data that I needed to process via event. And, being a good Javascript developer, wanted to make sure to handle asynchronous issues correctly. In my case, I wanted to make all the events were processed before continuing on to the next thing.
So, of course, it’s time to bring in promises here. In fact, I think I wanted to use promises for all of my event handlers instead of just the generic callbacks. It gives me a cleaner API to work with and is just more flexible if we need to start chaining things together.
Whenever promises are used, it’s very easy to fall into the deferred anti-pattern. Of course, everyone has been guilty of doing this one time or another when it’s a one-off kind of situation. But, I was planning on doing this throughout my application, so I needed to come up with something cleaner.
Lately, I’ve been using Bluebird as my promises library, especially in node. While digging through their documentation, I started to see that pretty much every example was trying to wrap the same type of API I was using. Basically, it was example after example of wrapping generic node.js methods into promises automatically.
Hey! I’m using generic node.js methods! This was made for me, right?
Since no one was around to answer my rhetorical question, I gave this modification to my global event object a try:
1 2 3 4 5 6 7 8 9 |
var events = require('events'); var Promise = require('bluebird'); var events = new events.EventEmitter(); events.emitAsync = Promise.promisify(events.emit); exports = module.exports = events; |
Cool. Now when I fire an event, I have a much cleaner API to deal with. There is no change to make on my event handler. In fact, no changes need to be made at all unless I want to use a promise instead of basic callback as they become interchangeable now. Here is the same code as above, except now I added a second call using the new Promises API.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
var events = require('./events'); // My events module // Add some handler events.on('cool.event', function(data, fn) { // Do some processing on data here // Now call the callback if it exists if (fn) // Use the standard err, data node.js callback format fn(undefined, data); }); // So this still says the same events.emit('cool.event', {data: 'awesome'}, function(err, data) { if (err) { // Handle an error if one should happen to show up } else { // Do something with data here } }); // Or we can use it this way events.emitAsync('cool.event', {data: 'awesome'}) .then(data) { // Do something with data here }) .fail(err) { // Handle an error if one should happen to show up }); |
The second way is much prettier if you ask me.
Now, here comes the best part. I can now easily handle my multiple events in a nice clean way that allows me to wait on them all the finish processing before moving on to the next piece of functionality.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var _ = require('lodash'); var Promise = require('bluebird'); var events = require('./events'); var entries = []; // Pretend I'm an array with 20 items to process // Use map to gather all promises from all entries in promises collection var promises = _.map(entries, function(entry) { return events.emitAsync('cool.event', entry); // Return one promise }); // Pass collection of promises to Promise.all, then wait for #then (see what I did there?) Promise.all(promises) .then(function() { // We only hit this when all the promises are resolved }); |
Notice: mc4wp_form is deprecated since version MailChimp for WordPress v3.0! Use mc4wp_show_form instead. in /var/www/html/wp-includes/functions.php on line 3888
Hi Ted,
Great stuff. Since you’re using Bluebird I would suggest looking at Promise.map (https://github.com/petkaantonov/bluebird/blob/master/API.md#mapfunction-mapper–object-options—promise) which will save you a bit of boilerplate and also allow your input array to contain Thenables as well as value objects.
Cheers,
Geoff
Awesome, thank you for that tip. I’m pretty new to Bluebird, so I haven’t quite dug into all it’s little secrets yet. I’ll definitely check it out.