In this article we will look into promises in Javascript. The concept of a real life promise is simple, as may also be the case when we first start using promises. However, as we gain more experience with Javascript, it becomes important to really understand promises.

When working with promises today we usually focus on the handling, and a promise is often linked with asynchronous operations like API calls, DB operations or IO calls. There are two important parts of a promise we need to understand: Creating and handling. Lets start by looking into the creation of a promise:

new Promise ( function ( resolve , reject ) { ... } ) ;

When creating a promise we pass a function that takes two paremeters, which are in turn two functions. When a promise is complete, either resolve or reject is called, each representing two key parts of a promise. The success of a promise, depending on the point of view, is often associated with resolve, and the faulting state is often associated with reject.

Resolve and Reject

Lets have a look at a real life example, and consider a scenario where a friend has made us a promise:

const friendKeepsHisWord = true ; const friendsPromise = new Promise ( function ( resolve , reject ) { if ( friendKeepsHisWord ) { resolve ( "My friend keeps his word" ) ; } else { reject ( "My friend is a liar" ) ; } } ) ; console . log ( friendsPromise ) ;

In this case the promise would be resolved right away to 'My friend keeps his word'. Simple right?

Promise {<resolved>: "My friend keeps his word"} __proto__: Promise [[PromiseStatus]]: "resolved" [[PromiseValue]]: "My friend keeps his word"

Additionally, a promise can have two other states. The pending state for this promise would look like this:

Promise {<pending>} __proto__: Promise [[PromiseStatus]]: "pending" [[PromiseValue]]: undefined

While the rejected state would look like this:

Promise {<rejected>: "My friend is a liar"} __proto__: Promise [[PromiseStatus]]: "rejected" [[PromiseValue]]: "My friend is a liar"

Prototypes

After a promise has been resolved or rejected, we often want to update something. This is where the prototype methods come in. One or two of the prototype methods will almost always execute depending on the implementation. When a promise is in the pending state, three of these prototype methods are really handy:

Let's add a setTimeout() to our code to use then() :

const friendKeepsHisWord = true ; const friendsPromise = new Promise ( function ( resolve , reject ) { setTimeout ( ( ) => { if ( friendKeepsHisWord ) { resolve ( "My friend keeps his word" ) ; } else { resolve ( "My friend is a liar" ) ; } } , 1000 ) ; } ) ; friendsPromise . then ( message => console . log ( message ) ) ;

If the promise is rejected with an error:

const friendKeepsHisWord = false ; const friendsPromise = new Promise ( function ( resolve , reject ) { setTimeout ( ( ) => { if ( friendKeepsHisWord ) { resolve ( "My friend keeps his word" ) ; } else { reject ( new Error ( "My friend is an Alien!!!!" ) ) ; } } , 1000 ) ; } ) ; friendsPromise . catch ( error => console . log ( error . message ) ) ;

If .finally() is implemented, it will call whenever a Promise is settled, regardless if the promise is rejected or fulfilled. Let's use it by adding a waitingOnFriend :

const friendKeepsHisWord = Math . floor ( Math . random ( ) * 5 ) !== 1 ? true : false ; let waitingOnFriend = true ; const friendsPromise = new Promise ( function ( resolve , reject ) { setTimeout ( ( ) => { if ( friendKeepsHisWord ) { resolve ( "My friend keeps his word" ) ; } else { reject ( "My friend is a liar" ) ; } } , 1000 ) ; } ) ; friendsPromise . then ( message => console . log ( message ) ) . catch ( error => console . log ( error ) ) . finally ( ( ) => { waitingOnFriend = false ; } ) ;

This image shows the flow of .catch() and .then() :

Here we see that .catch() and .then() also return a Promise , meaning they can be chained.

Rule of thumb when using Promises