Welcome to the Promised Land…of JavaScript, that is.

To start, JavaScript Promises are not a new concept but upon first glance, they may seem foreign.

Before we dive in, let’s go back to the basics.

1. What is a Promise?

As mentioned in Mozilla docs:

A Promise is a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers with an asynchronous action’s eventual success value or failure reason. This lets asynchronous methods return values like synchronous methods: instead of immediately returning the final value, the asynchronous method returns a promise to supply the value at some point in the future.

As you can see, the official description can be difficult to understand.

So what does synchronously/asynchronously actually mean when it comes to programming?

When a program is executed, it is combined with a series of tasks.

  • When a task is executed synchronously, it means that each task will have to be fully completed before moving on to the next task.
  • When it is executed asynchronously, it means that the program can move onto another task before the other one is finished. The execution of the asynchronous task will not affect the execution of the overall program.

javascript promises

Most people will agree that asynchronous programming is a complicated topic because it needs to perform careful and sophisticated techniques in order to control how things tie together and when they occur at the same time. As a solution to make asynchronous work more naturally and effortlessly, Promise was born.

But before we dive into the details of how JavaScript Promises works, let’s consider the following example.

Video Game Promises

Let’s say as a kid, your parents promise you they’ll inform you if they will buy you your absolute most favorite video game or not at the end of the week, based on your grades.

Until then, you’re not 100% positive you’ll get the video game by the end of the week. Your parents could buy it for you or decide not to buy it altogether for some reason.

These are the three stages of a promise.

  1. Pending: you don’t know whether you’ll get the video game at the end of the week.
  2. Resolved or Fulfilled: your parents buy you the video game.
  3. Rejected: you don’t get the video game because your parents are not happy with your grades.

Now, how does this relate to JavaScript Promises?

2. What are JavaScript Promises?

JavaScript Promises represent the eventual result of an asynchronous operation and serve as a placeholder which into the successful result value or reason for failure will materialize.  

When a Promise is pending it can either change to be fulfilled or changed into a rejected state. Once this occurs it will not evolve into another state and its value or failure reason will not change.

When a program is executed, an asynchronous task takes time to complete its execution (or fail) and then return a result. We know that it will come at some time in the future but the program cannot ensure when exactly the event will occur.

A Promise acts like a proxy or a placeholder between the program and the asynchronous task. It promises the program that it will inform the program when the execution of the asynchronous task is completed or if it has failed, all without affecting the execution or result of the asynchronous tasks. Instead of immediately returning the final value, the asynchronous method returns a promise to supply the value at some point in the future.

The program must pre-define the scenarios of what to do when the asynchronous task is completed and when it fails.

3. How do Promises work?

A Promise in its life will always be in one of these states:

  • pending: initial state, neither fulfilled nor rejected. This state is when it is created using a new Promise constructor.
  • fulfilled: meaning that the operation was completed successfully.
  • rejected: meaning that the operation failed.

Creating Promises syntax:

  1. var promise = new Promise( /* executor */ function(resolve, reject) {   
        // Do something, possibly an async task.  
          
        if (/* Everything turned out fine */) {  
            resolve(/* Maybe the result of the async tasks etc */);  
            // After this the state of this Promise becomes fulfilled.  
            // When resolve function is called, it means that this Promise notified   
            // the completion of the asynchronous task being moderated.  
        }  
        else {  
            reject(Error("Some error message"));  
            // After this, the state of this Promise becomes rejected.  
            // When resolve function is called, it means that this Promise notified   
            // the end of the asynchronous task with an error.  
        }  
    } );

The created Promise object wraps the asynchronous execution to moderate what happened inside the executor.

Once it (the Promise) gets declared, the executor gets called immediately. The executor is called before the Promise constructor even returns the created object.

The executor normally initiates some asynchronous tasks, and once that completes, if everything turns out fine then it calls the resolve function to resolve the Promise or else rejects it if an error has occurred.

.then() method

The object promise of type Promise has the method then(). This method takes two functions as its parameters.

One is a function that will be passed the resolved value of the Promise once it is fulfilled.

The other is a function to be called if the Promise is rejected.

Consider the following example. Note that in this program, we estimate that 1 second means 1 minute. And we use the setTimeout() function (kind of asynchronous) for an easy illustration:

  1. var thawChicken = tineToThaw_minutes => {  
        console.log("Chicken is being thawed...");  
      
        return new Promise((resolve, reject) => {  
        let isSomethingWrong = false;  
      
        setTimeout(() => {  
            if(!isSomethingWrong){  
            resolve("Thawed chicken"); // <= This will be the argument of the first function parameter of then method.  
            }else{  
            reject(Error("Something wrong happended!")); // <= This error object will be passed as the argument to the second function parameter of then method.  
            }  
      
        }, tineToThaw_minutes * 1000);  
        });  
    }  
      
    thawChicken(3 /* minutes */).then(  
        chicken => console.log("What we have after waiting? - " + chicken),  
        error   => console.log("Error message: " + error.message)  
    );

For a live example, please refer here: https://jsbin.com/hesimasoni/edit?js,console

If you change isSomethingWrong to true in the above example, you will see the error notified.

.catch() method

As you can see in the above example, the Promise has both a success and an error handler.

But what happens if the success handler throws an error? There’s nothing there to catch it. As a result, the error gets swallowed.

This is where the .catch() method comes in like so:

  1. thawChicken(3 /* minutes */).then(  
        chicken => console.log("What we have after waiting? - " + chicken),  
        error   => console.log("Error message: " + error.message)  
    ).catch(errorHander);

It is recommended to end all promise chains with a .catch()!

4. Promise Rules

  • When a Promise is initiated, its state is pending. This Promise can only be changed into a resolved or a rejected state.
  • A resolved or rejected Promise is settled, and it must not transition into any other state even if you reset it.
  1. new Promise((resolve, reject) => {  
      resolve('resolved!');  
      resolve('re-set resolved!');  
      reject('rejected!');  
    }).then(  
      mes => console.log(mes),  
      err => console.log(err)  
    );

The output will be: “resolved!”

  • Once a Promise is settled, it must have a value (which may be undefined). That value must not change.

5. Chaining

Because the .then() method always returns a Promise, we can chain them together to transform values or to run additional async actions one after another in a queue and control the flow of asynchronous tasks all together in an easy and natural way in lexical.

Transform values

  1. new Promise((resolve, reject) => {  
      resolve('message');  
    }).then(  
      mes => mes + ' to transform'  
    ).then(  
      transformedMessage => console.log(transformedMessage)  
    );

in this case, the  output will be: “message to transform.

In this example, when I got the message from the above Promise, the new message is transformed to the next as transformedMessage by returning it to the previous message.

Control asynchronous tasks flow

By returning an async task, we can also chain them to run async actions in a sequence flow.

This is an advantage of Promise over the classical callback functions to avoid what can be referred to as callback hell.

  1. var thawChicken = tineToThaw_minutes => {  
      console.log("Chicken is being thawed...");  
      
      return new Promise((resolve, reject) => {  
        let isSomethingWrong = false;  
      
        setTimeout(() => {  
          if (!isSomethingWrong) {  
            resolve("Thawed chicken"); // <= This will be the argument of the first function parameter of then method.      
          } else {  
            reject(Error("Something wrong happended!")); // <= This error object will be passed as the argument to the second function parameter of then method.      
          }  
      
        }, tineToThaw_minutes * 1000);  
      });  
    }
      
    var marinateChicken = (chicken, spices) => {  
      console.log("Marinating chicken with spices...");  
      
      return new Promise((resolve, reject) => {  
        setTimeout(() => {  
          resolve("Marinated Chicken");  
        }, 5000);  
      });  
    }  
      
    thawChicken(3 /* minutes */ ).then(  
      chicken => {  
        console.log("What we have after waiting? - " + chicken);  
        let spices = ["Salt", "Sugar", "Pepper", "Garlic"];  
        return marinateChicken(chicken, spices);  
      },  
      error => console.log("Error message: " + error.message)  
    ).then(  
      marinatedChicken => console.log("After marinating we have: " + marinatedChicken);
    );

Another example can be found here: https://jsbin.com/xuquyikojo/edit?js,console
By using chaining, the code looks much more natural.

6. Promise.all

As the Mozilla docs:

The Promise.all(iterable) method returns a single Promise that resolves when all of the promises in the iterable argument have resolved or when the iterable argument contains no promises. It rejects with the reason of the first promise that rejects.

Because Promise.all returns a Promise, it is then able to perform its function. The returned Promise is fulfilled when all Promises have passed to all the resolved methods. This helps us to define an action to be performed when all promises are completed.

Promise.all gets rejected when any of passed-in Promises re rejected despite whether or not other Promises have been resolved.

Conclusion

As you can see, JavaScript Promises have become an integral part in using JavaScript. In addition, they provide a simpler alternative for executing, composing and managing asynchronous operations.