Promises
I don’t know if everybody is using them yet, but if you’re putting off like I was, here are two things about Promises that may convince you to start using them:
- Error handling
- Collections
Note: I’ve only really used bluebird so apologies if these things aren’t universal to all Promise libraries
Error handling
If you’ve done a crazy amount of nested callbacks before, you know what this one’s about. Handling errors all the way down a trail of nested callbacks is fairly straightforward, but it’s so repetitive that it can be tempting to not do it at all.
Example (yes, my examples are NodeJS-specific):
// app is an express or restify app
app.get( '/route', function( req, res, next ) {
fs.readFile( 'path/to/file.json', { encoding: 'utf8' }, function( err, contents ) {
if ( err ) {
return next( err );
}
var data = JSON.parse( contents );
request( 'http://service.com/api/get_info?parameter=' + data.parameter, function( err, response ) {
if ( err ) {
return next( err );
}
if ( response.statusCode !== 200 ) {
err = new Error( response.body );
return next( err );
}
res.send( response.body );
});
});
});
First of all, we know nested callbacks are a bad idea. Second of all, this
whole if error throw error...
thing is repetitive and unnecessary with the right
abstraction.
Same example with promises:
app.get( '/route', function( req, res, next ) {
fs.readFileAsync( 'path/to/file.json', { encoding: 'utf8' } )
.then( function( contents ) {
var data = JSON.parse( contents );
// This example is about error handling, but this feature
// of promises is awesome. If a then callback returns a
// promise, the next then callback will have that promise's
// resolved values as its arguments.
return requestAsync( 'http://service.com/api/get_info?parameter=' + data.parameter );
})
.then( function( response ) {
if ( response.statusCode !== 200 ) {
var err = new Error( response.statusCode + ': ' + response.body );
throw err;
}
res.send( response.body );
})
.catch( function( err ) {
next( err );
});
});
Much cleaner, no? Notice too, that I’m using Express error handling here. In
the first example, I call next
repeatedly. In the Promisified version, it’s
only called once.
Collections
Without promises:
app.get( '/assets', function( req, res, next ) {
var ids = req.query.ids.split( ',' );
var len = ids.len;
var data = [];
var stop = false;
var dataCallback = function( err, response ) {
if ( err ) {
stop = true;
next( err );
}
if ( !stop ) {
data.push( response );
if ( data.length >= len ) {
res.send({
request_id: req.requestId,
data: data
});
}
}
};
_.each( ids, function( id ) {
metadataService.getInfo( id, dataCallback );
});
});
This is the coolest… Watch this:
app.get( '/assets', function( req, res, next ) {
Promise.all(
_.map( req.query.ids.split( ',' ), function( id ) {
return $metadataService.getInfo( id );
});
)
.then( function( data ) {
res.send({
request_id: req.requestId,
data: data
});
})
.catch( function( err ) {
next( err );
});
});
What’s going on here? The endpoint is set up to take a comma-separated list of
image ids. I create an array of promises by calling $metadataService.getInfo
for each id. Promise.all
lets you wrap an array of promises and bind to the
completion of all of them. I can’t tell you what a lifesaver this has been!