Much of the purpose of promises is to allow the developer to serialize and reason about independent asynchronous actions. This can be accomplished by utilizing the Promise chaining feature.
The code, links, and a live example of this are available at .
The promise handler definition method then()
returns another promise, which can have further handlers defined upon it—in a handler called chain:
var successHandler = () => { console.log('called'); }; var p = new Promise((resolve, reject) => { resolve(); }) .then(successHandler) .then(successHandler) .then(successHandler); // called // called // called
Chained handlers can pass data to their subsequent handlers in the following manner:
var successHandler = (val) => { console.log(val); return val+1; }; var p = new Promise((resolve, reject) => { resolve(0); }) .then(successHandler) .then(successHandler) .then(successHandler); // 0 // 1 // 2
Returning normally from a promise handler (not the executor) will, by default, signal child promise states to become resolved. However, if either the executor or the subsequent handlers throw an uncaught exception, they will, by default, reject; this will serve to catch the exception:
const p = new Promise((resolve, reject) => { // executor will immediately throw an exception, forcing // a reject throw 123; }) .then( // child promise resolved handler data => console.log('resolved', data), // child promise rejected handler data => console.log('rejected', data)); // "rejected", 123
Note that the exception, here a number primitive, is the data that is passed to the rejection handler.
A Promise reaching a final state will trigger child promises to follow it in turn. This simple but powerful concept allows you to build broad and fault-tolerant promise structures that elegantly mesh collections of dependent asynchronous actions.
The topology of promises lends itself to some interesting utilization patterns.
Promise handlers will execute in the order that the promises are defined. If a promise has multiple handlers attached to a single state, then that state will execute all its handlers before resolving the following chained promise:
const incr = val => { console.log(val); return ++val; }; var outerResolve; const firstPromise = new Promise((resolve, reject) => { outerResolve = resolve; }); // define firstPromise's handler firstPromise.then(incr); // append another handler for firstPromise, and collect // the returned promise in secondPromise const secondPromise = firstPromise.then(incr); // append another handler for the second promise, and collect // the returned promise in thirdPromise const thirdPromise = secondPromise.then(incr); // at this point, invoking outerResolve() will: // resolve firstPromise; firstPromise's handlers executes // resolve secondPromise; secondPromises's handler executes // resolve thirdPromise; no handlers defined yet // additional promise handler definition order is // unimportant; they will be resolved as the promises // sequentially have their states defined secondPromise.then(incr); firstPromise.then(incr); thirdPromise.then(incr); // the setup currently defined is as follows: // firstPromise -> secondPromise -> thirdPromise // incr() incr() incr() // incr() incr() // incr() outerResolve(0); // 0 // 0 // 0 // 1 // 1 // 2
Since the return value of a handler decides whether or not the promise state is resolved or rejected, any of the handlers associated with a promise is able to set the state—which, as you may recall, can only be set once. The defining of the parent promise state will trigger the child promise handlers to be executed.
It should now be apparent how the trees of the promise functionality can be derived from the combination of promise chaining and handler chaining. When used properly, they can yield extremely elegant solutions for difficult and ugly asynchronous action serializations.
The catch()
method is a shorthand for promise.then(null, errorCallback)
. Using it can lead to slightly cleaner promise definitions, but it is nothing more than syntactical sugar:
var outerReject; const p = new Promise((resolve, reject) => { outerReject = reject; }) .catch(() => console.log('rejected!')); outerReject(); // "rejected"
It is also possible to chain p.then().catch()
. An error thrown by the original promise will propagate through the promise created by then()
, cause it to reject, and reach the promise created by catch()
. It creates one extra level of promise indirection, but to an outside observer, it will behave the same.