javascript promises/q library
DESCRIPTION
Presentation I gave to the node.dc meetup group March 13, 2013 on using Promises and the Q library to make flow of control easier to reason about in Javascript code using async and callbacksTRANSCRIPT
Javascript Promises/Q libraryJonathan Altman
node.dc March 2013@async_io
http://async.io/
What is a Promise? The simplest explanation: it is an easy way to avoid writing the Pyramid of Doom
Pyramid of Doomstep1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// Do something with value4
});
});
});
}); // from https://github.com/kriskowal/q
Why Promises? What about?
• async: http://github.com/caolan/async
• step: https://github.com/creationix/step
• flow-js: https://github.com/willconant/flow-js
• ...you get the point
Non-Promises Fix:async.series({
normalize: function(callback){// Bodies removed for brevity
}, compose: function(callback){}, invert: function(callback){}// A bunch more stuff killed
}, function (err, results){if (!err ) {err = new Error('Results did not contain a valid image buffer')
}else {callback(err, results.imageBuffer);
}}
}); //https://github.com/jonathana/heatNode/blob/master/lib/imageGen/generateimages.js
Is That Not Good Enough?var later = Q.nfcall(nodedc.wait_a_few_slides);
// or: we’ll come back to it
Promises: Longer Explanation“A Promise is an object representation of an event. In the course of its life, a Promise goes from a pending state, when it’s called, to a resolved or rejected state, when it’s been completed, or it could also stay pending forever and is never resolved.”
http://flaviocopes.com/deferred-and-promises-in-javascript/
Say What?
• Promises take a call to an asynchronous function and wrap it with an object whose methods proxy when the wrapped function either completes or errors
• A good Promise library also provides a set of control methods on that object wrapper to handle composition of multiple Promise-ified asynchronous method calls
• Promises use the best qualities of an object--encapsulation of state--to track the state of an asynchronous call
Promises provide asolid abstractionfor representing the
state of an asynchronous calland writing flow of
control code based on that state
Pyramid of Doom Againstep1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// Do something with value4
});
});
});
}); // from https://github.com/kriskowal/q
Pyramid of Doom on PromisesQ.fcall(step1) // This returns a Promise obj.then(step2).then(step3).then(step4).then(function (value4) { // Do something with value4}, function (error) { // Handle any error from step1 through step4}).done(); // from https://github.com/kriskowal/q
Again, Why Promises?
• It’s a spec: http://wiki.commonjs.org/wiki/Promises/A
• Generally supported by a bunch of libs both browser and server-side:
• jQuery (sort of, supposedly doesn’t fully work like Promises/A)
• AngularJS
• Q library (https://github.com/kriskowal/q)
• Provides separation of concerns between wrapping and flow of control handling of deferred activities
later.then(function(){Provides separation of concerns between wrapping and flow of
control handling of deferred activities
// that separation is the key
});
Promises can (mostly) be shared across libraries
Sharing
• Many libraries can exchange Promise objects
• AngularJS’ Promise API is explicitly based off a subset of Q. If Q is loads first, AngularJS just uses that
• jQuery’s promises can be consumed by Q, with some adaptations
So Why Q?
• Q consumes Promises from most other libraries
• Pure Javascript library
• Can be used both client-side (browser or e.g. Phonegap) and server-side (npm install q, but you’re using package.json, right?)
• Provides utilities to make it easy to write Promise-based code or wrap non-Promise-based functions
• Provides a library of methods to build control flow around Promise results and errors
• Small (enough?): ~1400 SLOC, ~ 8.5kb minified
Generating Promises With Q
Q.fcall/nfcall: Wrap an Async method with a Promise
function writeError(errMessage) { return Q.nfcall(fs.writeFile, "errors.log", errMessage);}• nfcall: node function call. Sort of a misnomer, original intent was to
make it easy to wrap node library/module calls into Promises, but works for any async call
• fcall: turns functions synchronously returning a value into a Promise, • You can make it so it’s Promises all the way down (at least until you hit
the turtles)
Q.defer: Interject Promise Support• Use when you have to intermingle other logic inside callback work
function getLocation() { var deferred = Q.defer(); console.log("Calling getCurrentPosition"); navigator.geolocation.getCurrentPosition(function(position) { deferred.resolve(position); console.log("getCurrentPosition resolved"); }, function(error){ deferred.reject(error); console.log("getCurrentPosition errored"); }); return deferred.promise; };
Q.when: Wrapping Other Libraries’ Promises
• From the Q documentation: “Not all promise libraries make the same guarantees as Q and certainly don’t provide all of the same methods. Most libraries only provide a partially functional then method.”
return Q.when($.ajax(...)).then(function () {});
Control Flow and Error Handling
Simple Flow• Use then/fail (and .done() to avoid swallowing unhandled exceptions)
• You can chain then calls
Q.fcall(step1) // This returns a Promise obj.then(step2).then(step3).then(step4).then(function (value4) { // Do something with value4}, function (error) { // Handle any error from step1 through step4}).done(); // from https://github.com/kriskowal/q
More Complex Handling
• Q.reduce(): Chain indeterminate-length sequential chains
• all(): Turn multiple Promises in an array into a single Promise. Fails at the first failure from any Promise, returning that failure
• allResolved(): Turn multiple Promises in an array into a single Promise. Succeeds when all Promises complete, resolved or failed, and resolves with the array of Promises
Testing
Mocha + Chai + Chai-as-Promised
• Mocha: http://visionmedia.github.com/mocha/ -- unit testing framework
• Chai: http://chaijs.com/ -- BDD add-on for Mocha
• Chai-as-promised: https://github.com/domenic/chai-as-promised -- Promises-enables Chai/Mocha
Mocha + Chai + Chai-as-Promised
• Adds testability methods to promises supporting BDD
• Note the use of done, which signals the async part of mocha
it('Should error on short barcode format', function(done){ var promise = lookupBarcode(lookupData.shortGtinData.gtin); promise.should.be.rejected.and.notify(done);});
Resources
• Q javascript library: https://github.com/kriskowal/q
• Q documentation: http://documentup.com/kriskowal/q/
Thank you. Questions?