http://ghost.deceptacle.com/2014/03/26/javascript-promises-are-awesome/
26 Mar 2014
I have completely fallen in love with Javascript promises. There's plenty of other blog posts going into more depth about the pros and cons of promises, so I'll include some relevant ones that I enjoyed at the bottom, but I just wanted to outline a few features/tricks that I use that show some of their power.
A promise library can let you wrap an array of promises in a larger promise, letting you fire off a number of asynchronous requests, and then processing them once they are all completed.
Example
// Populate an array with some promises
var promises = [];
promises.push(getTrack(1));
promises.push(getTrack(2));
promises.push(getTrack(3));
Q.all(promises)
.then(function (tracks) {
// All of my tracks are loaded!
})
.fail(function (err) {
// Oh no! A lookup failed :(
});
Easy as pie!
If, for some reason, I don't want to fail the entire chain if one promise lookup fails, Q
provides a function called allSettled
, which works similarly to all
, but returns an object with each result wrapped up. Using the array from above:
Q.allSettled(promises)
.then(function (results) {
results.forEach(function (result) {
if (result.state === 'fulfilled') {
// This item was loaded!
console.log(result.value);
} else {
// This item failed to be loaded :(
console.log(result.reason);
}
});
});
This is one of those "oh I'm so clever" hacks that I came up with a little while back and have been refining it a bit. The basic idea is that you can use promises recursively to continue resolving an asynchronous call if you need to (for example, searching through paged data, or handling failed calls).
Here's an example that I'm using on a personal project:
// This function retrieves data from the provided array of URIs
var get = function (uris, depth) {
var promises = [];
depth = depth || 0;
// Make sure we don't recurse forever!
if (depth >= 3) {
// Return a promise that returns an empty array
return Q.fcall(function () {
return [];
});
}
uris.forEach(function(uri) {
// The lookup function is what actually makes a
// singular request
promises.push(lookup(uri));
});
return Q.allSettled(promises)
.then(function (results) {
var failed = [];
var succeeded = [];
results.forEach(function (result) {
if (result.state === 'fulfilled') {
succeeded.push(result.value);
} else {
// Note: the lookup call adds the uri to the
// error object
failed.push(result.reason.uri);
}
});
if (failed.length === 0) {
// No failed lookups, just pass along the succeeded items
return succeeded;
} else {
return get(failed, depth + 1)
.then(function join(results) {
// Join the results from the recursive call to the
// succeeded object
return succeeded.concat(results);
});
}
});
}
I know this is a fair amount to process, but I think it's pretty self explanatory overall. If all the calls succeed without an issue, then it works much like the example above regarding the Multiple Asynchronous Calls. Where this shines is when one of those calls fails, we can just continue returning promises until the call succeeds, or we've tried too many times. We take the items pushed into the failed
array, and return a recursive call to get
, which returns a promise. Whatever initially called get
will continue processing when we've tried fetching the data multiple times.
Now, this could be fleshed out a bit to return the failed results if needed, but that wouldn't be too difficult. It would probably follow a similar output as the allSettled
function, providing a wrapper around the result set.
These were just two things about promises that I really love. There are a number of other ways that promises can be leveraged to make asynchronous code easier to manage, but these two are some recent examples that I've used recently, and wanted to share them.
then
function.