Presented by @trevorburnham
mainWindow.menu("File", function(err, file) {
if(err) throw err;
file.openMenu(function(err, menu) {
if(err) throw err;
menu.item("Open", function(err, item) {
if(err) throw err;
item.click(function(err) {
if(err) throw err;
window.createDialog('DOOM!', function(err, dialog) {
if(err) throw err;
...
});
});
});
});
});
Instead of doing this…
myLameFunction(myLameCallback); // ugh, so 1995
…you can do this:
myGreatPromise = myAwesomeFunction();
myGreatPromise.then(myTremendousCallback); // add callbacks later
myGreatPromise.then(myEvenBetterCallback); // stack ∞ callbacks
myGreatPromise.then(null, myErrorHandler); // reuse error logic
A Promise represents a task (usually async) that can either succeed or fail. In Promise-ese, we say that the task was either fulfilled* or rejected.
aBeautifulPromise.then(
function() { /* This runs if the Promise is fulfilled... */ },
function() { /* ...and this runs if the Promise is rejected */ }
);
*In jQuery, Promises are said to be resolved on success. But the Promises/A+ spec uses the word “resolved” to mean “either fulfilled or rejected,” and Promises/A+ is awesome.
The real magic of Promises is that you can chain them to represent a series of async tasks:
function RunThreeTasks() {
function logError(e) {
console.error(e);
throw e; // reject the Promise returned by then
}
var task1 = startTask1();
var task2 = task1.then(startTask2);
var task3 = task2.then(startTask3);
var allTasks = task3.then(null, logError);
return allTasks;
}
The Promise returned by ThreeStepTask
is fulfilled only when all three tasks have succeeded. If any task fails, logError
is called, and the returned Promise is rejected.
“The thing is, promises are not about callback aggregation. That’s a simple utility. Promises are about something much deeper, namely providing a direct correspondence between synchronous functions and asynchronous functions.”
—You’re Missing the Point of Promises,
by @domenic
jQuery doesn't recognize Promises/A+ Promises, but all of the major Promises/A+ libraries recognize jQuery Promises:
standardizedPromise = when(jQueryPromise);
You can even monkey-patch jQuery (overriding $.Deferred
) to make it return standards-compliant Promises: http://jsfiddle.net/jdiamond/ZSpJX/
(err, results)
callbackRemember that chaining example?
function RunThreeTasks() {
function logError(e) {
console.error(e);
throw e; // reject the Promise returned by then
}
var task1 = startTask1();
var task2 = task1.then(startTask2);
var task3 = task2.then(startTask3);
var allTasks = task3.then(null, logError);
return allTasks;
}
This is a waterfall: Each task starts when the last one completed successfully.
Here’s how we’d write that same example using async.waterfall
:
function RunThreeTasks(callback) {
async.waterfall([task1, task2, task3], function(err) {
if (err) {
console.error(e);
};
callback.apply(null, arguments);
});
}
(Here the task
functions take an (err, result)
callback rather than returning a Promise.)
Run an array of async tasks in series, call the callback when all tasks have completed (or one fails):
async.waterfall(tasks, function(err, lastResult) {
// ...
});
What if we want to iterate through an arbitrary array of Promise-returning task functions?
function promiseWaterfall(tasks) {
finalTaskPromise = tasks.reduce(function(prevTaskPromise, task) {
return prevTaskPromise.then(task);
}, resolvedPromise); // initial value
return finalTaskPromise;
}
Functional idioms FTW!
Run an array of async tasks simultaneously, call the callback with the results when all tasks have succeeded or an error when one has failed:
async.parallel(tasks, function(err, results) {
// ...
});
Run an array of async tasks simultaneously, return a Promise that’s fulfilled with the results when all tasks have succeeded or rejected with an error when one has failed:
function promiseParallel(tasks) {
var results = [];
taskPromises = tasks.map(function(task) {
return task();
});
return when.all(taskPromises);
}
This is such a common idiom that every major Promises implementation comes with an implementation: jQuery calls it when()
, Q/when/RSVP call it all()
.
function all(promises) {
finalPromise = promises.reduce(function(prevPromise, promise, i) {
return prevPromise.then(function(results) {
return promise.then(function(result) {
results.push(result);
return results;
})
});
}, resolvedPromise); // a Promise that resolved with []
return finalPromise;
}
Given an arbitrary graph of identifiers, dependencies, and functions, run those functions in an acceptable order:
// Example adapted from the Async.js docs:
async.auto({
get_data: getData,
make_folder: makeFolder,
write_file: ['get_data', 'make_folder', writeFile],
email_link: ['write_file', emailLink]
});
“So we’ve created a correct optimizing module loader with barely any code, simply by using a graph of lazy promises. We’ve taken the functional programming approach of using value relationships rather than explicit control flow to solve the problem, and it was much easier than if we’d written the control flow ourselves as the main element in the solution.”
—Callbacks are Imperative, Promises are Functional,
by @jcoglan
// Module is defined at https://blog.jcoglan.com/2013/03/30/
new Module('getData', [], getData);
new Module('makeFolder', [], makeFolder);
new Module('writeFile', ['getData', 'makeFolder'], writeFile);
new Module('emailLink', ['writeFile'], emailLink);