Presented by @trevorburnham
at Mobile+Web DevConf 2013
The npm Book: The Essential Guide to the Node Package Manager
// Send score to server, give callback the rankings it returns
function submitPlayerScore(score, onSuccess, onError) {
post('/score', score, {
success: onSuccess,
error: onError
});
}
// Send score to server, show score animation, invoke callback
// with rankings when the animation and network call both complete
function submitPlayerScore(score, onSuccess, onError) {
var animationCompleted, rankings;
post('/score', score, {
success: function(data) {
rankings = data;
if (animationCompleted) onSuccess(rankings);
},
error: onError
});
startScoreAnimation(function() {
animationCompleted = true;
if (rankings) onSuccess(rankings);
});
}
// Get auth token, send score and token to server, show score
// animation, invoke callback with rankings when that's all done
function submitPlayerScore(score, onSuccess, onError) {
var animationCompleted, rankings;
get('/token', {
success: function(token) {
post('/score', {token: token, score: score}, {
success: function(data) {
rankings = data;
if (animationCompleted) onSuccess(rankings);
},
error: onError
});
},
error: onError
});
startScoreAnimation(function() {
animationCompleted = true;
if (rankings) onSuccess(rankings);
});
}
function submitPlayerScore(score) {
scoreAnimation.start();
var token = get('/token');
var data = {token: token, score: score};
var rankings = post(data, '/score');
// Wait for animation to complete
while (!scoreAnimation.isComplete) {
Thread.sleep(0);
}
return rankings;
}
Instead of doing this…
myLameFunction(myCallback); // ugh, so 1995
…you can do this…
myGreatPromise = myAwesomeFunction();
…and then all of this:
myGreatPromise.then(myCallback); // add callbacks later
myGreatPromise.then(myExtraCallback); // 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 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
The real magic of Promises is that you can chain them to represent a series of async tasks:
// Perform three async tasks sequentially
function RunThreeTasks() {
var task1 = startTask1();
var task2 = task1.then(startTask2);
var task3 = task2.then(startTask3);
return task3;
}
The Promise returned by RunThreeTasks
is fulfilled only when all three tasks have succeeded, and rejected if any of the tasks fails.
https://github.com/promises-aplus/promises-spec
var after = before.then(onFulfilled, onRejected);
after
starts in a pending
state. If before
is fulfilled:
onFulfilled
is not a function, after
is fulfilled with the same value as before
.onFulfilled
returns a value (other than a Promise), after
is fulfilled with that value.onFulfilled
throws an exception, after
is rejected with that exception.onFulfilled
returns a Promise, after
will do whatever that Promise does!The same chaining rules apply to the onRejected
handler, which means that errors cascade to the next Promise:
// Perform three async tasks sequentially, log any error
function RunThreeTasks() {
var task1 = startTask1();
var task2 = task1.then(startTask2);
var task3 = task2.then(startTask3);
task3.then(null, logError);
return task3;
}
To make a Promise that you can fulfill or reject yourself, you typically create a Deferred object that contains both a Promise and methods that can resolve it.
Here's an example using Q.js:
function waitJustOneSecond() {
var deferred = Q.defer();
setTimeout(function() {
deferred.resolve('Thanks for your patience!');
}, 1e3);
return deferred.promise;
}
Turning a callback function into a Promise-returning function is easy!
// Invoke callback the next time the user taps the screen
function onNextTap(callback) {
...
}
// Fulfill promise the next time the user taps the screen
function promisifiedOnNextTap() {
var deferred = Q.defer();
onNextTap(deferred.resolve);
return deferred.promise;
}
Every major Promises library comes with a function that takes a list of Promises and returns a Promise with these properties:
jQuery calls this function when()
.
Q/when/RSVP call it all()
.
// Get auth token, send score and token to server, show score
// animation, invoke callback with rankings when that's all done
function submitPlayerScore(score) {
var getTokenPromise = get('/token');
var submitScorePromise = getTokenPromise.then(function(token) {
return post('/score', {token: token, score: score});
});
var scoreAnimationPromise = startScoreAnimation();
return Q.all([submitScorePromise, scoreAnimationPromise]);
}
jQuery doesn't recognize Promises/A+ Promises, but all of the major Promises/A+ libraries recognize jQuery Promises:
standardizedPromise = Q.when(jQueryPromise);
You can even monkey-patch jQuery (overriding $.Deferred
) to make it return standards-compliant Promises: http://jsfiddle.net/jdiamond/ZSpJX/
Promises are coming to the DOM!*
http://dom.spec.whatwg.org/#futures
*Not actually in any browsers yet...
Slides: http://trevorburnham.com/presentations/sane-async-with-promises