Promise API Recap

Let’s review the ES6 Promise API that we’ve already seen unfold in bits and pieces throughout this chapter.

Note: The following API is native only as of ES6, but there are specification-compliant polyfills (not just extended Promise libraries) which can define Promise and all its associated behavior so that you can use native Promises even in pre-ES6 browsers. One such polyfill is “Native Promise Only” (http://github.com/getify/native-promise-only), which I wrote!

new Promise(..) Constructor

The revealing constructor Promise(..) must be used with new, and must be provided a function callback that is synchronously/immediately called. This function is passed two function callbacks that act as resolution capabilities for the promise. We commonly label these resolve(..) and reject(..):

  1. var p = new Promise( function(resolve,reject){
  2. // `resolve(..)` to resolve/fulfill the promise
  3. // `reject(..)` to reject the promise
  4. } );

reject(..) simply rejects the promise, but resolve(..) can either fulfill the promise or reject it, depending on what it’s passed. If resolve(..) is passed an immediate, non-Promise, non-thenable value, then the promise is fulfilled with that value.

But if resolve(..) is passed a genuine Promise or thenable value, that value is unwrapped recursively, and whatever its final resolution/state is will be adopted by the promise.

Promise.resolve(..) and Promise.reject(..)

A shortcut for creating an already-rejected Promise is Promise.reject(..), so these two promises are equivalent:

  1. var p1 = new Promise( function(resolve,reject){
  2. reject( "Oops" );
  3. } );
  4. var p2 = Promise.reject( "Oops" );

Promise.resolve(..) is usually used to create an already-fulfilled Promise in a similar way to Promise.reject(..). However, Promise.resolve(..) also unwraps thenable values (as discussed several times already). In that case, the Promise returned adopts the final resolution of the thenable you passed in, which could either be fulfillment or rejection:

  1. var fulfilledTh = {
  2. then: function(cb) { cb( 42 ); }
  3. };
  4. var rejectedTh = {
  5. then: function(cb,errCb) {
  6. errCb( "Oops" );
  7. }
  8. };
  9. var p1 = Promise.resolve( fulfilledTh );
  10. var p2 = Promise.resolve( rejectedTh );
  11. // `p1` will be a fulfilled promise
  12. // `p2` will be a rejected promise

And remember, Promise.resolve(..) doesn’t do anything if what you pass is already a genuine Promise; it just returns the value directly. So there’s no overhead to calling Promise.resolve(..) on values that you don’t know the nature of, if one happens to already be a genuine Promise.

then(..) and catch(..)

Each Promise instance (not the Promise API namespace) has then(..) and catch(..) methods, which allow registering of fulfillment and rejection handlers for the Promise. Once the Promise is resolved, one or the other of these handlers will be called, but not both, and it will always be called asynchronously (see “Jobs” in Chapter 1).

then(..) takes one or two parameters, the first for the fulfillment callback, and the second for the rejection callback. If either is omitted or is otherwise passed as a non-function value, a default callback is substituted respectively. The default fulfillment callback simply passes the message along, while the default rejection callback simply rethrows (propagates) the error reason it receives.

catch(..) takes only the rejection callback as a parameter, and automatically substitutes the default fulfillment callback, as just discussed. In other words, it’s equivalent to then(null,..):

  1. p.then( fulfilled );
  2. p.then( fulfilled, rejected );
  3. p.catch( rejected ); // or `p.then( null, rejected )`

then(..) and catch(..) also create and return a new promise, which can be used to express Promise chain flow control. If the fulfillment or rejection callbacks have an exception thrown, the returned promise is rejected. If either callback returns an immediate, non-Promise, non-thenable value, that value is set as the fulfillment for the returned promise. If the fulfillment handler specifically returns a promise or thenable value, that value is unwrapped and becomes the resolution of the returned promise.

Promise.all([ .. ]) and Promise.race([ .. ])

The static helpers Promise.all([ .. ]) and Promise.race([ .. ]) on the ES6 Promise API both create a Promise as their return value. The resolution of that promise is controlled entirely by the array of promises that you pass in.

For Promise.all([ .. ]), all the promises you pass in must fulfill for the returned promise to fulfill. If any promise is rejected, the main returned promise is immediately rejected, too (discarding the results of any of the other promises). For fulfillment, you receive an array of all the passed in promises’ fulfillment values. For rejection, you receive just the first promise rejection reason value. This pattern is classically called a “gate”: all must arrive before the gate opens.

For Promise.race([ .. ]), only the first promise to resolve (fulfillment or rejection) “wins,” and whatever that resolution is becomes the resolution of the returned promise. This pattern is classically called a “latch”: first one to open the latch gets through. Consider:

  1. var p1 = Promise.resolve( 42 );
  2. var p2 = Promise.resolve( "Hello World" );
  3. var p3 = Promise.reject( "Oops" );
  4. Promise.race( [p1,p2,p3] )
  5. .then( function(msg){
  6. console.log( msg ); // 42
  7. } );
  8. Promise.all( [p1,p2,p3] )
  9. .catch( function(err){
  10. console.error( err ); // "Oops"
  11. } );
  12. Promise.all( [p1,p2] )
  13. .then( function(msgs){
  14. console.log( msgs ); // [42,"Hello World"]
  15. } );

Warning: Be careful! If an empty array is passed to Promise.all([ .. ]), it will fulfill immediately, but Promise.race([ .. ]) will hang forever and never resolve.

The ES6 Promise API is pretty simple and straightforward. It’s at least good enough to serve the most basic of async cases, and is a good place to start when rearranging your code from callback hell to something better.

But there’s a whole lot of async sophistication that apps often demand which Promises themselves will be limited in addressing. In the next section, we’ll dive into those limitations as motivations for the benefit of Promise libraries.