Tuesday, 31 October, 2017 UTC


Summary

Promises represent the eventual result of an asynchronous operation. They give us a way to handle asynchronous processing in a more synchronous fashion. A promise represents a value we can handle in the future, with the following guarantees:
  • promises are immutable,
  • promises are either kept or broken,
  • when a promise is kept, we are guaranteed to receive a value
  • when a promise is broken, we are guaranteed to receive the reason why the promise cannot be fulfilled

Promise states

  • pending: may transition to fulfilled or rejected
  • fulfilled (kept promise): must have a value
  • rejected (broken promise): must have a reason for rejecting the promise

Creating promises in ES6

let promise1 = new Promise( function( resolve, reject ) {
    // call resolve( value ) to resolve a promise
    // call reject( reason ) to reject a promise
} );

// Create a resolved promise
let promise2 = Promise.resolve( 5 );
When instantiating a promise, the handler function decides whether to resolve or reject the promise. When you call resolve, the promise moves to Fulfilled state. When you call reject, the promise moves to Rejected state.
Promise.resolve( value ) creates a promise that’s already resolved.

Handling the fulfilled or rejected states

Promises can be passed around as values, as function arguments, and as return values. Values and reasons for rejection can be handled by handlers inside the then method of the promise.
promise.then( onFulfilled, onRejected );
  • then may be called more than once to register multiple callbacks. The callbacks have a fixed order of execution
  • then is chainable, it returns a promise
  • if any of the arguments are not functions, they have to be ignored
  • onFulfilled is called once with the argument of the fulfilled value if it exists
  • onRejected is called once with the argument of the reason why the promise was rejected
Examples:
let promisePaymentAmount = Promise.resolve( 50 );

promisePaymentAmount
    .then( amount => {
        amount *= 1.25;
        console.log( 'amount * 1.25: ', amount );
        return amount;
    } ).then( amount => {
        console.log( 'amount: ', amount );
        return amount;
    } );
Notice the return value of the callback function of the first then call. This value is passed as amount in the second then clause.
let promiseIntro = new Promise( function( resolve, reject ) {
    setTimeout( () => reject( 'Error demo' ), 2000 );
});

promiseIntro.then( null, error => console.log( error ) );
Instead of
promise.then( null, errorHandler );
you can also write
promise.catch( errorHandler );
to make error handling more semantic. It is best practice to always use catch for handling errors, and place it at the end of the promise handler chain. Reason: catch also catches errors thrown inside the resolved handlers. Example:
var p = Promise.resolve( 5 );

p.then( ( value ) => console.log( 'Value:', value ) )
 .then( () => { throw new Error('Error in second handler' ) } )
 .catch( ( error ) => console.log( 'Error: ', error.toString() ) );
As p is resolved, the first handler logs its value, and the second handler throws an error. The error is caught by the catch method, displaying the error message.

Handling multiple promises

Promise.all() takes an iterable object of promises. In this section, we will use arrays. Once all of them are fulfilled, it returns an array of fulfilled values. One any of the promises in the array fails, Promise.all() also fails.
var loan1 = new Promise( (resolve, reject) => { 
  setTimeout( () => resolve( 110 ) , 1000 ); 
}); 
var loan2 = new Promise((resolve, reject) => { 
  setTimeout( () => resolve( 120 ) , 2000 ); 
});
var loan3 = new Promise( (resolve, reject) => {
  reject( 'Bankrupt' );
});

Promise.all([ loan1, loan2, loan3 ]).then( value => { 
  console.log(value);
}, reason => {
  console.log(reason);
} );
The output of the above code is Bankrupt, and it’s displayed immediately.