Skip to main content

Command Palette

Search for a command to run...

Demystifying Promises in JavaScript

Updated
•5 min read•View as Markdown
Demystifying Promises in JavaScript

Promise is one of the most confusing concept in JavaScript. Especially for beginners and it's the case because there are plenty of moving parts to a simple Promise.

To understand Promises in JavaScript you need to understand each moving part and put it togehter as a beautiul puzzle.

Spoiler alert ! There are 4 pieces to this puzzle. (Keep counting 🤭).

Brace yourself, this is going to be a complex article! But I promise if you stick till the end, you won't have any doubts left.

So what are Promises?

Promises are a way to handle asynchronous operations in JavaScript!

You must have read this somewhere on the internet, whenever you searched about promises.

Before understanding Promises lets take a stepback and understand the 1st and most important piece of the puzzle : asynchronous operations .

Asynchronous Operations

Asynchronous operations can be defined as operations that provide the results not immediately but after some time ( e.g : any I/O operation like reading a file from the memory or making a network call to fetch some data ). So instead of waiting for them we keep on executing the remainder of the code and handle them when we get their results.

The keyword here being handle.

There are primarily two ways to handle them.

  • Callbacks
  • Promises

Callbacks is another way to handle asynchronous operations, but it has it's own quirks ( Callback Hell ) that brought us to another way of handling aynchronous operations : Promises. 🥳

The Concept of Promise

Simply put a Promise is an object passed as a placeholder (proxy) for data that we don't actually have at the current moment.

The core idea behind Promises is that a Promise represents the result of an asynchronous operation (1st piece in the puzzle). We know that an asynchronous operation can either be pending, successful or fail . This brings us to the 2nd piece of our puzzle : The states of a Promise .

A Promise has three states.

  • pending - The initial state of a promise.
  • fulfilled - The state of a promise representing a successful operation.
  • rejected - The state of a promise representing a failed operation.

Once a promise is fulfilled or rejected it's state cannot be changed.

Working with Promises

So now we have understood the Concept of Promise let's see how we can create a Promise. This is the 3rd piece of our puzzle.

Don't worry generally we don't create our own Promises, but this is also another reason why we don't understand Promises. So this is an important part, Don't Skip it.

Creating our own Promise

As we learned just now that, Promise is simply an object , so to create a Promise object, what do we need ?

A Constructor function ! (so obvious, duh 😒)

The Promise Constructor function accepts a function as an argument, which in turn takes two call back functions as arguments, that are called on completion of the asynchronous operation

  • resolve() : Changes the state of promise from pending to fulfilled.
  • reject() : Changes the state of promise from pending to rejected.

We can also pass arguments to resolve() and reject(), that can be used by us later on in our code.

A code example of the above process.

const promise = new Promise((resolve, reject) => {
    /**
        Some asynchronous task.
    **/
    //data can be the result of the async task.
    if (data) {
        //change state of promise from pending to fulilled.
        resolve("Data Found")
    } else {
        //change state of promise from ending to rejected.
        reject("Data not Found")
    }
})

Congrats, you have successfully created your Promise.

Now comes the 4th piece of the puzzle : How to handle the asynchronous operation's result ?

Consuming Promises

As we know know a Promise is an object, it has 2 very useful methods in it.

  • then() : If the promise state changes from pending —> fulfilled, then() is executed.
  • catch(): If the promise state changes from pending —> rejected, catch() is executed.

Both then() & catch() methods again take callback functions as arguments and return a Promise object.

This is the reason why we are able to chain many .then() methods one after the another followed by .catch() in the end.

Putting it all together

Since we now have all the 4 pieces of our Promise Puzzle. Let's put all these pieces together and see them in action.

function asyncOperation() {
    return new Promise((resolve, reject) => {
        //Making an API call
        if (data) {
            //if the file read was success or 
            //If the N/W call gave us correct data
            resolve(data);
        } else {
            //error object we get back from failed req
            //or a custom error object
            reject(error);
        }
    });

    const executeMeIfDataComes = (realData) => {
        //manipulate the real data as much as you want.
    }

    const executeMeIfErrorOccurs = (error) => {
        //OOPs error occured.
        //Handle error like the Good Dev You are?
    }

    const asyncResult=asyncOperation();

    asyncResult.then(executeMeIfDataComes).catch(executeMeIfErrorOccurs);

The most beautiful thing about Promises is that it auto-injects the arguments passed to resolve() and reject(), in the callback function of .then() and .catch() .

Which allows us to deal with the result of the asynchronous operation.

Summary

Briefly summarizing the above article. The Promise Puzzle has 4 pieces to it. Each piece answers a specific question.

  1. The need for a Promise ? To handle asynchronous code.
  2. What a Promise is ? A proxy object, representing the result of an asynchronous operation.
  3. How to create a Promise ? The Promise Constructor function.
  4. How to deal with Promises ? By passing callback functions to .then() & .catch() methods of a promise object.

BTW I made a promise to you guys in the beginning.

So did my promise get fulfilled 😄, rejected 😢 or is still pending ☹️ . Do let me know in the comment below.