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.