DEV Community

Vladislav Zimnikov
Vladislav Zimnikov

Posted on • Edited on

Modern JavaScript: Promise

Table of contents

  1. Introduction
  2. Promise
  3. First Examples
  4. Handling Promise
    1. then()
    2. catch()
    3. finally()
    4. Chaining
  5. Advanced Usage
    1. Server Request
    2. Multiple Requests
    3. First to settle
    4. First to fulfill
  6. Outro

Introduction

When it comes to JavaScript it executes code one line after another so next line of code will not start it's execution up until previous one have finished

But what if required operation takes too much time? Should we freeze whole application's execution waiting for result? Obviously, not

Most common and easily understandable example is request to some server. You never know how much time exactly there is needed to receive response. It may take few dozens of milliseconds or few seconds

And while waiting for response we still want our application to be responsive, usable and even let customer know about making server request like through showing some sort of loader animation

small loader image that is used to let customer know that something is being loaded

In addition, once application receives response it should process it somehow so we also need a way to tell the program how it should treat data it is going to receive in future and we are not able to know when exactly

Basically, we want to be able to tell our program such thing:

In background, without blocking whole execution process, make this request, wait for the response and once received perform given set of commands

But how can we achieve such kind of functionality if currently we are aware that JavaScript executes all of the code in strict sequence?

Promise

In short, Promise allows performing operations asynchronously

The concept of Promise is pretty straightforward:

Give it piece of code you want to execute asynchronously and specify in what case Promise should fulfil successfully and vice versa

To understand principle of Promise better before diving into examples few theory concepts have to be discussed

First of all, when we create Promise with code to process asynchronously, we should pass this code in form of callback.

Callback - function passed as an argument for another function that is going to be executed at some specific point of latter function execution

Callback that is being passed to promise is called executor

Executor - callback function, invoked by Promise with two arguments being passed: resolve and reject

resolve - callback function that should be called when operation inside of Promise finishes successfully. Usually, accepts one parameter - result of operation performed inside of executor

reject - callback function that should be called in case when error happens during executor's code processing

In terms of making server request we would call resolve with response as an argument right after receiving it to proceed with it's further processing

First Examples

When we need to create promise we have to use constructor of Promise class with keyword new. Basically, we create instance of class Promise

As it was mentioned earlier, to create promise we should pass executor

Simplest example of how to create new Promise:

new Promise((resolve, reject) => {})
Enter fullscreen mode Exit fullscreen mode

Currently, our promise does nothing. To actually see, how promise executes code in async manner let me show you next example

new Promise((resolve, reject) => { setTimeout(() => { console.log('in promise'); }, 1000) }) console.log('after promise')

You can run this code right in browser or anywhere else to see what happens. First message to console we receive is 'after promise' and only after 1 second message 'in promise' is being printed out.

Function setTimeout allows to delay execution of code and I am going to use this feature to demonstrate more capabilities of promises.

Handling Promise

Basically, promise can be fulfilled or rejected. If code inside of an executor processed well, callback resolve is being invoked and promise starts fulfilling. In opposite case, callback reject is called and rejection happens.

Promise gives us, developers, possibility to control what has to be done in case of promise fulfilment and rejection.

Object of Promise provides two methods for such thing: then and catch. Both accept callback

then()

To understand better, let's firstly set up our playground. First of all, I will create promise and save it to constant. Inside of an executor I will utilize setTimeout to postpone invocation of resolve function up to 1 second. To resolve I will pass string 'Hello Promise'

const ourPromise = new Promise((resolve, reject) => {
    setTimeout(() => resolve('Hello Promise'), 1000);
});
Enter fullscreen mode Exit fullscreen mode

Now, let's use .then() for the first time. I know that promise will pass string to callback on fulfilling, so let's print received message

ourPromise
    .then((message) => console.log(message));
Enter fullscreen mode Exit fullscreen mode
const ourPromise = new Promise((resolve, reject) => { setTimeout(() => resolve('Hello Promise'), 1000); }); ourPromise .then((message) => console.log(message)); console.log('after promise');

If you run code above you can see that message 'after promise' is being printed first, just like last time. Only difference is that message 'Hello Promise' is being printed in callback passed to .then() as an argument.

catch()

In case if executor's code fails, Promise should be rejected by reject invocation. To actually handle Promise's rejection, you should pass callback to .catch() method of Promise instance. Usually, error being passed as an argument.

Let's consider next code example:

const ourPromise = new Promise((resolve, reject) => { setTimeout(() => reject('Rejecting Promise'), 1000); }); ourPromise .catch((message) => console.log(message)); console.log('after promise');

It's almost the same as previous one, but we call reject instead of resolve and replace .then() with .catch(). As a result we get the same output in console.

finally()

It's pretty common occasion when you need to perform code on Promise's settlement despite the outcome. To avoid duplication of logic in .then and .catch methods there is .finally.

Callback passed to method .finally is going to be executed in any case: either Promise resolved successfully or was rejected

const ourPromise = new Promise( (resolve, reject) => { if (Math.random() > 0.5) { reject('Rejecting'); } resolve('Resolving'); } ); ourPromise .then(msg => console.log(msg)) .catch(err => console.log(err)) .finally(() => console.log('Final action'));

Important note

callback passed to finally does not accept any arguments

Chaining

Methods .then, .catch and .finally return promise itself, so they can be chained. That's what makes working with Promises neat and concise. Try to run below example multiple times and see how in different cases different callbacks are being invoked

const ourPromise = new Promise( (resolve, reject) => { if (Math.random() > 0.5) { reject('Rejecting'); } resolve('Resolving'); } ); ourPromise .then(msg => console.log(msg)) .catch(err => console.log(err)) .finally(() => console.log('final action'));

Another important thing is that you are not limited to one .then invocation, for example. There can be occasions when you receive another promise while handling previous one and to process new promise we can just return it and use .then again. Let me just show you

const ourPromise = new Promise( (resolve, reject) => { if (Math.random() > 0.5) { reject('Rejecting'); } resolve('Resolving'); } ); ourPromise .then(msg => msg + ' x2') .then(msg => console.log(msg)) .catch(err => console.log(err)) .finally(() => console.log('final action'));

As you can see from first callback I returned original message plus some extra characters. Value, returned from previous callback is being passed to next one.

Now let me show example with multiple Promises:

const ourPromise = new Promise( (resolve, reject) => { if (Math.random() > 0.5) { reject('Rejecting'); } resolve('Resolving'); } ); ourPromise .then( msg => new Promise((resolve) => resolve('Another Promise')) ) .then(msg => console.log(msg)) .catch(err => console.log(err)) .finally(() => console.log('final action'));

If you run code above and first promise is going to be resolved then you will see that message we passed to resolve function inside of our second Promise successfully got to callback we passed to our second .then. This way we can handle multiple consecutive Promises in beautiful and convenient way

Advanced Usage

Server Request

Modern JS libraries and APIs for making and handling requests to server are built around Promise. Client side Fetch API is built around Promise

Let me show you one example using quite well-known library axios

const axios = require('axios'); axios.get('https://jsonplaceholder.typicode.com/users') .then(res => console.log(res.data)); console.log('Something after request');

As you can see, we were able to perform request and process it response without blocking execution process

Multiple Requests

Sometimes there is need to perform multiple requests simultaneously. Specifically for this reason one of the methods provided by Promise can be utilised.

const axios = require('axios'); const requests = [ axios.get('https://jsonplaceholder.typicode.com/users'), axios.get('https://jsonplaceholder.typicode.com/posts'), axios.get('https://jsonplaceholder.typicode.com/comments') ] Promise.all(requests) .then(res => console.log(res))

Promise.all() accepts iterable object like array, for instance, with promises we need to wait for. Once all Promises are resolved or at least one is rejected, then result of method all which is Promise itself is resolved or rejected

First to settle

If you are waiting on multiple promises and you need to perform some action once one of them resolves or rejects there is method for that: Promise.race

There is an example:

const ourPromise = new Promise( (resolve, reject) => { setTimeout(() => { if (Math.random() > 0.5) { reject('Rejecting 1'); } resolve('Resolving 1'); }, Math.floor(Math.random() * 1000)) } ); const ourPromise2 = new Promise( (resolve, reject) => { setTimeout(() => { if (Math.random() > 0.5) { reject('Rejecting 2'); } resolve('Resolving 2'); }, Math.floor(Math.random() * 1000)) } ); Promise.race([ourPromise, ourPromise2]) .then(res => console.log(res)) .catch(res => console.log(res));

First to fulfill

If you want to perform action once first among many promises fulfils there is solution for you as well: Promise.any. Be aware that Promise.any exists in Node since version 15 so you might need to change the version of runner below

const ourPromise = new Promise( (resolve, reject) => { setTimeout(() => { if (Math.random() > 0.5) { reject('Rejecting 1'); } resolve('Resolving 1'); }, Math.floor(Math.random() * 1000)) } ); const ourPromise2 = new Promise( (resolve, reject) => { setTimeout(() => { if (Math.random() > 0.5) { reject('Rejecting 2'); } resolve('Resolving 2'); }, Math.floor(Math.random() * 1000)) } ); Promise.any([ourPromise, ourPromise2]) .then(res => console.log(res)) .catch(res => console.log(res));

Outro

That's it for today. Hope you enjoyed this little article.

Don't hesitate to leave a comment, I would appreciate it.

Have a nice day :)

Top comments (0)