Want to see the full-length video right now for free?
Promises are an abstraction that makes working with asynchronous code more manageable and consistent. Rather than passing a callback function to an asynchronous operation (and possibly falling into the dreaded callback pyramid of doom), the asynchronous operation can return a "Promise" representing the future value, or failure.
Here's an example in Node for finding the largest file in a directory (taken from strongloop):
var fs = require('fs')
var path = require('path')
module.exports = function (dir, cb) {
fs.readdir(dir, function (er, files) {
if (er) return cb(er)
var counter = files.length
var errored = false
var stats = []
files.forEach(function (file, index) {
fs.stat(path.join(dir,file), function (er, stat) {
if (errored) return
if (er) {
errored = true
return cb(er)
}
stats[index] = stat
if (--counter == 0) {
var largest = stats
.filter(function (stat) { return stat.isFile() })
.reduce(function (prev, next) {
if (prev.size > next.size) return prev
return next
})
cb(null, files[stats.indexOf(largest)])
}
})
})
})
}
...and here's that same code, using the Q promises library:
var fs = require('fs')
var path = require('path')
var Q = require('q')
var fs_readdir = Q.denodeify(fs.readdir)
var fs_stat = Q.denodeify(fs.stat)
module.exports = function (dir) {
return fs_readdir(dir)
.then(function (files) {
var promises = files.map(function (file) {
return fs_stat(path.join(dir,file))
})
return Q.all(promises).then(function (stats) {
return [files, stats]
})
})
.then(function (data) {
var files = data[0]
var stats = data[1]
var largest = stats
.filter(function (stat) { return stat.isFile() })
.reduce(function (prev, next) {
if (prev.size > next.size) return prev
return next
})
return files[stats.indexOf(largest)]
})
.catch(console.log(error));
}
Let's go through all of that:
We're using
Q.denodeify
to create Q-compatible functions from Node functions. The then
function takes
a function to execute once the promise is finished. We can chain .then
(like
Q(function).then(...).then(...)
) to run a bunch of promises one after each
other. The all
function takes an array of promises and wraps it in a promise
that resolves once all the promises have resolved.
The core idea of promises is that rather than passing a function that represents the next step (a "callback"), the promise inverts control and returns an object that represents the future state where we've resolved the asynchronous action and allows our code to maintain control over the flow of execution.
The A+ spec is the specification for how promises must behave. Promises
start in the pending state and can move to resolved or rejected. The then
method takes two arguments, onFulfilled
and onRejected
. onFulfilled
takes
the value of the successful computation, and onRejected
takes the reason for
rejection. then
can be called on the same promise multiple times and their
functions will run in the order they were called.
This code creates a promise that waits 3 seconds then transitions to the
resolved
state with the value 42
:
// A Promise that returns a value
function theUltimateAnswer() {
return new Promise(function(resolve) {
setTimeout(function(){
var value = 42;
resolve(value);
}, 3000);
});
}
promise = theUltimateAnswer()
// Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
promise
// Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
promise
// Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 42}
This code creates a promise that waits 3 seconds, then rejects:
// A Promise that just fails
function waitForIt() {
return new Promise(function(resolve, reject) {
setTimeout(function(){
reject("Oh noes!");
}, 3000);
});
}
promise = waitForIt()
// Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
promise
// Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
// Uncaught (in promise) Oh noes!(anonymous function) @ VM194:6setTimeout
promise
// Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: "Oh noes!"}
This code sample shows how to use the value from a successful promise:
function howDoIGetThatValue() {
return new Promise(function(resolve) {
setTimeout(function(){
var value = 42;
resolve(value);
}, 1000);
});
}
howDoIGetThatValue().then(function(result) {
console.log(result);
});
This code sample shows how to handle a rejected promise:
// A Promise that just fails
function waitForIt() {
return new Promise(function(resolve, reject) {
setTimeout(function(){
reject("Oh noes!");
}, 1000);
});
}
promiseWithThen = waitForIt().then(null, function(error){
console.log(error);
});
promiseWithCatch = waitForIt().catch(function(error){
console.log(error);
});
Errors will propagate along the chain:
// Where does it fail?
function waitForIt() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
if (Math.random() < 0.5) {
reject("Oh noes!");
} else {
resolve("Everything is fine.");
}
}, 1000);
});
}
waitForIt().then(function(result) {
throw("Just kidding!");
}).catch(function(error) {
console.log(error);
});
The following code is taken from electron-boilerplate:
module.exports = function () {
return init()
.then(copyRuntime)
.then(cleanupRuntime)
.then(packageBuiltApp)
.then(finalize)
.then(renameApp)
.then(createInstaller)
.then(cleanClutter)
.catch(console.error);
};
request.then(function () {
modalForm.form.removeClass('loading');
});
return this.get('favoritedItems').then(items => {
return Ember.RSVP.Promise.all(
items.mapBy('topics')
).then(topics => {
return Ember.RSVP.Promise.all(
topics.mapBy('items')
);
});
});