Understanding Javascript Promises

javascript_promises

Javascript Promises and Promises in general are not a new concept in the world Javascript but they were not very popular until they were officially supported in ES6. Javascript Promises are not difficult themselves, however many people find them a hard to approach and dive into at the beginning. So, shall we begin?

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.

Do we still have your attention? As always, the official description can be hard to decipher upon first glance especially for beginners. We hope  that after reading this article, you can come back to this definition and understand what it was originally intended to mean.

One aspect to note in the definition is that the word “asynchronous” is repeated three times. It suggesting that a Promise is something related to an asynchronous execution manipulation.

Before we get too far ahead of ourselves. Let’s take a step back to understand the difference between synchronously and asynchronously in programming:

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

  • When a task is executed synchronously, it means that the task will have to wait for it to be finished before moving on to the next task.
  • When it’s done asynchronously, it means that the program can move on to another task before it gets finished and the execution of the asynchronous task will not affect the execution of the program.

javascript_promises

Many people may agree that synchronous programming is a quite complicated topic because it needs to perform careful and sophisticated techniques in order to control how things tie together when they’re occurring at the same time.

Because of this, if you’d like to make the process of working with asynchronous much easier, that’s where Promises were born and became natively supported in Javascript. This tool helps us to deal with controlling asynchronous tasks in Javascript.

But before diving into further details on Javascript Promises work, let’s consider the following scenario:

Imagine you’re at a restaurant having lunch and you order a set lunch with fried chicken, rice, and a salad.

Have you ever thought about the process of how they make the food and serve you your order? We like to think of it like this: There are three parties cooperating with each other to prepare your place of food. And let’s assume they operate in different parts of the kitchen.

The cook prepares the chicken, the sous chef prepares the salad, and the third person cooks the rice.

The flow or work can be abstracted like so:

  • Job I: The cook frying the chicken:
      1. Step 1. Take the raw chicken out from the refrigerator to thaw
      2. Step 2. Marinate the chicken with various spices for an hour
      3. Step 3. Fry the chicken in the pan until its juices run clear, its skin is pierced with a knife and it looks ready to eat
  • Job II: The sous chef prepares the salad:
      1. Step 1. Sort out which vegetables will be in the salad
      2. Step 2. Mix the vegetables with mayonnaise or another type of seasoning or dressing
  • Job III: The third chef who cooks rice:
    1. Step 1. Wash rice
    2. Step 2. Cook the rice

In order to perform each step and to reach the final step of the whole project (i.e. bringing the plate to the customer), one must wait for the previous step to be completed. But the process of each job should not block the whole kitchen’s flow of work. The result of the current step may impact the next step to reach completion. In this case, the frying of the chicken must not block the whole kitchen’s execution.

For instance, in order to marinate the chicken, you have to thaw it first. And you have to have your chicken marinated with spices before frying or sauteing it with oil.

While the chicken is being made, the salad and the rice are also in the process of being prepared.

Javascript Promises manage tasks almost the same way the kitchen executes a job (frying the chicken, making the salad and cooking the rice). This is like asynchronous tasks in a program.

Enough about food. Let’s take these concepts to a real program.

What is a Promise in Javascript?

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

Javascript Promises act like proxies between the program and the asynchronous task. It makes a promise to the program that it will inform the program when the execution of the asynchronous task is completed or if is has failed. All in all, it will not affect 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.

javascript_promises

How do Promises work?

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

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

Creating Promises syntax:

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 around the asynchronous execution in order to moderate what is happening inside the executor.

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

The executor normally initiates some asynchronous tasks, and once they’re completed, if everything turned out ok, it then calls the resolve function in order to resolve the promise or else it rejects it if an error has occurred.

.then() method

The object promise of this type of Promise has the method then(). This method takes two types of functions into 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 on if the Promise is rejected.

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

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)  
)

You can review a live example here.

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 then an error gets swallowed.

So that’s where the .catch() method comes into play. Simply, use it like this:

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

We recommend ending all promise chains with a .catch()!

Promise Rules

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

This will output: “resolved!”

  • Once a promise is settled, it must have a value (which may be undefined). In other words, that value must not change.

Chaining

Because the .then() method always returns a Promise, we can chain ‘thens’ together in order to transform values or to run additional async actions one after another. These will be run in a queue which also control the flow of asynchronous tasks all together in an easy and natural way.

Transform values

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

Output will be: “message to transform.

In this example, when we have the message from the above Promise, we transform the new message to the next then as then transformedMessage by returning it in the previous then.

Control asynchronous tasks flow

By returning an async task, we can also chain thens to run async actions in a sequence flow. This is an advantage of a Promise over the classical callback to avoid what many like to note as “callback hell.”

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);
);

You can view a live example here.

By using chaining, the code looks so natural in lexical.

Promise.all

A Google search will explain that::

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. The returned promise is fulfilled when all promises passed to the all method have been resolved. This helps us to define an action to be performed when all promises are done.

Promise.all get rejected when any of the passed-in promises are rejected despite whether or not the other promises have been resolved.

And there you have it. Hopefully after reading this blog post Javascript Promises and Promises in general won’t be as intimidating as they appear upon first glance. We promise. 🙂

Pangara

Think you have what it takes to join our exclusive network of freelance developers and programmers? Learn more about becoming a Pangara Talent today.

 

Duc Filan
Full Stack Developer

Duc is a full stack developer based in Japan. He is passionate about full stack development because he’s able to control all stages of the process — from beginning to end. Needless to say, he loves to code in addition to reading books and sharing his knowledge with other people. Contact him at DucFilan@gmail.com.

Subscribe for Top Tech Content
Subscribe for Top Tech Content