Welcome to the Promise land! (sorry) You have been sneakily using promises this whole class, but today, we will illuminate what exactly a promise is, and shed light on best practices to use while playing with promises.
- Describe the anatomy of a Promise
- Utilize Promises to manage asynchronous operations
- Correctly use a Promise chain
- Handle errors while working with Promises
Promises are an alternative to directly using callbacks. Promises allow us to write asynchronous code that looks like synchronous code. Promises create the illusion of returning values or throwing errors from within our callbacks. While promises do not replace callbacks--promises depend on callbacks--they provide a layer of abstraction between you and callbacks, enabling you to prevent callback hell.
A promise represents a value that will be available for use in the future, but is not available now. Think of it like an IOU for the actual value. Once it resolves, it will pass the value it's standing in for to a function you provide for it to invoke. Like an IOU, it can also "bounce", or reject, and fail to provide the value it's standing in for. Promises also provide a way to deal with this possibility.
A promise takes an anonymous function with two parameters; resolve
and reject
.
The promise resolves when resolve
is called, and is rejected when reject
is
called.
The value passed to resolve
will be the argument passed to the resolution
handler you register by calling the promise's then
method. Likewise with the
value passed to reject
and the rejection handler registered by calling
the promise's (or a chained promise's) catch
method.
These two methods on every Promise
object are the primary means of interacting
with them. This section covers what they have in common.
They can be used the same way. Each takes one argument, a function. Here is a
usage example featuring an arbitrary promise, somePromise
:
somePromise.then(function(resolutionValue) {
console.log("somePromise resolved with value " + resolutionValue);
doSomethingWith(resolutionValue);
});
somePromise.catch(function(rejectionValue) {
console.error(rejectionValue instanceof Error ?
rejectionValue :
"somePromise rejected with value " + rejectionValue);
});
then
is a method on every Promise
object. It is used to register an event
handler for the promise's "resolve" event. When the promise resolves, the handler
is invoked and passed the value the promise resolved to as its argument.
catch
is a method on every Promise
object. It is used to register an event
handler for the promise's "reject" event. When the promise rejects, the handler
is invoked and passed the value (usually an Error
object) the promised rejected
with as its argument.
Promises allow us to reclaim the return
and throw
keywords. We can return a value from our then
handler, and that value will be used to fulfill the promise returned by then
. It will be available as a parameter to the following then handler.
We can throw errors from our then
handler, and they will be used to reject the promise returned by then
. It will be available as a parameter to the nearest catch handler down the chain.
There is a little more to then
that this. It returns a new promise that resolves
(or rejects) based on whether the upstream promise resolves or rejects, and what
goes on in its resolution handler. Here is a code demonstration:
somePromise.then(function(resVal) {
console.log("somePromise resolved!");
if(Math.random() > .5) {
return "The promise returned by `then` will now also resolve.";
} else {
throw new Error("The promise returned by `then` will now reject!");
}
}).then(function(rV) {
console.log("The promise returned by the previous call to `then` resolved!");
});
Promise.then
returning a new promise is what enables us to build
promise chains. A chained promise resolves only if its upstream promise
resolves AND the resolution handler does not throw an error. Otherwise, it
rejects. This is important to understanding the next section.
Like above, there's a little more to catch
. Since then
lets us build promise
chains, and chained promises reject if their upstream promise rejects, catch
can be used at the end of a promise chain to catch all upstream rejections. Here
is a code example:
somePromise.then(function(rV) {
if(Math.random() > .5) {
return "Resolve 1!";
} else {
throw new Error("Reject 1!");
}
}).then(function(resVal) {
if(Math.random() > .5) {
return "Resolve 2!";
} else {
throw new Error("Reject 2!");
}
}).then(function(resolutionValue) {
if(Math.random() > .5) {
return "Resolve 3!";
} else {
throw new Error("Reject 3!");
}
}).catch(function(err) {
console.error(err);
});
A tricky aspect of promises is iterating over an array of them. Looping doesn't work.
Enter Promise.all
.
It lets you provide a handler to be called once all async functions have finished. Promise.all
collapses an array of promises into a single promise, which resolves once all its constituent promises have resolved.
Head over to the Promises Lab and clone it.