DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for The complete guide to understanding Promise in JavaScript
Emmanuel Fordjour  Kumah
Emmanuel Fordjour Kumah

Posted on

The complete guide to understanding Promise in JavaScript

Promise is one of the most essential concept to understand in JavaScript, yet it remains a challenging concept to grasp. Promise assist us to handle asynchronous operations in JavaScript.

The concept of asynchronous in JavaScript enables us to break down complex projects into minor tasks.
We subsequently use any of these procedures : callbacks, promise or async/await to execute these minor tasks more effectively.

In this article, I intend to explain Promise to you in an easy-to-understand procedure. The article will explore the ensuing concepts

  • Asynchronous and Synchronous operations
  • Callback and Callback hell
  • Promise and the Promise lifecycle
  • Promise Chaining
  • Promise error handling

Before we dive into Promise, we need to first understand synchronous and asynchronous operations in JavaScript.

Synchronous and Asynchronous Operations

JavaScript is a single-thread language, because it is single-threaded, it only authorizes one logic to be performed at a time. Code execution is in sequence, and must complete executing a statement before moving on to the next statement.

The procedure defined above makes the operations in JavaScript synchronous.

Understanding Synchronous Operations

In a synchronous system, tasks are completed one after the other.

Consider a scenario where you are tasked to lift 5 buckets filled with water at varying levels from one destination to the other, and empty it into a bigger reservoir.

Synchronous JS

As long as you have only one hand, you complete the lifting of one bucket to its destination, pour the water into the required reservoir, before moving on to lift and empty the next bucket into the reservoir.

Tasks are being completed one after the other making it synchronous

There is always a challenge in performing these tasks one after the other (lifting, moving and emptying the buckets into the reservoir).

For example, if there is difficulty in lifting the third bucket due to the high volume of water in it, it will prevent the completion of the other tasks. The other buckets (buckets 4 to 10) will have to wait for the current bucket to be moved to its destination and emptied into the reservoir before their session begins.

In effect, the third bucket is delaying its task hence blocking all the remaining tasks from being completed.

From the scenario above, if tasks are being completed one after the other in JavaScript, the procedure is synchronous, and synchronous operations in JavaScript is blocking.

For example, if a statement takes a while to execute, it will freeze the other statements from completing.

Let's take a look at Asynchronous operations.

Understanding Asynchronous Operations

JavaScript is a single-threaded language, however, JavaScript can behave as if it were multi-threaded and non-blocking. This means that, it doesn't wait for the response of an API call, I/O events, etc., and can proceed the code execution.

Asynchronous programming is a technique that enables your program to start a potentially long-running task, and still be able to be responsive to other events while that task runs, rather than having to wait until that task has finished. Once that task has finished, your application is presented with the result.

Let's explore this technique further.

Take the same scenario where you have 5 buckets filled with water at different levels. But in this scenario, you have 5 helping hands (multiple hands) to lift the buckets from a point to their destination, and empty into the reservoir.

Because you have 5 helping hands, each hand can perform its task independently.

If one hand struggles to lift it assigned bucket to its destination due to the volume of water it contains, it can take all the time it needs to lift and empty the bucket into the reservoir, and this will not stop the other hands from completing their tasks.

With the scenario above, each task is complete independently, making it asynchronous.

Asynchronous operations in JavaScript is non-blocking. Meaning, if a statement takes a while to execute, it will not block the other statements from completing.

The challenge with long-running synchronous function.

In the above, we explored both synchronous and asynchronous operations in JavaScript.

To recap:

  • In synchronous operations: tasks are *performed one after the other, and is blocking *
  • In asynchronous operations: tasks are performed independently of other tasks, and is non-blocking

Let's explore some challenges we might encounter with synchronous operations.

What if a synchronous operation takes a long time to complete, and there is a task which depends on an output from our earlier task (which is delaying).

What will happen to the rest of our tasks?

Well, we have no option, but to wait for the long running task to complete, before we can move on.
This will freeze or block all the other tasks that might depend on the output of our long running task

To figure out the issue of freezing our application, we need a way for our application to:

  • Commence a long-running or delaying operation by calling a function. This function will execute when the delaying operation has completed.
  • Inform us with the result of the long-running operation when it eventually completes.
  • Run the other tasks independently.

That's exactly what asynchronous functions try to solve. If a function takes some time to complete its task, the rest of the code below that function can still run whiles the function is in progress, making our application non-blocking

Example of asynchronous operation

// Declare some functions
function foo() {
    console.log("foo")
}
function bar() {
    console.log("bar")
}

//Executes the functions below
setTimeout(foo,2000) //Delays for 2s then executes foo

console.log("Faz")
bar()
Enter fullscreen mode Exit fullscreen mode

By default, the execution of the code above should be synchronous and blocking, meaning tasks should executed one after the other.

See the illustration below:

Async

Because setTimeout() function will wait for 2s before running the foo, it should block or freeze the other codes below it from executing.

However, the output of the code is:

"Faz"
"bar"
"foo"
Enter fullscreen mode Exit fullscreen mode

Due to the JavaScript event-loop behavior, the entire code executes asynchronously and non-blocking.

This occurs because the event loop first executes all synchronous code and at the same time, running the setTimeout() in the background.

In the background, it waits for 2 seconds for the setTimeout()to complete, and when it's done, puts the callback that we passed as an argument in something called the task queue where it will be added to the call stack and executed.

Hence, the program above is executed independently and is non-blocking

Understanding callback functions?

One approach to asynchronous programming, is to make functions that perform a slow action take an extra argument, a callback function. The action is started in the background, and when it finishes, the callback function is called.

In the code above, you will notice, we passed foo as an argument to the setTimeout() function.

A callback is a function that's passed into another function, with the expectation that, the passed function will be called at the appropriate time.

The code below illustrates callback

function task1(call_task2){

  console.log("Start task1, when done, start task2 ")
 //call task 2
  call_task2()
}

function task2(){
  console.log("Task2 has started...")
}
//pass task2 to task1
task1(task2)
Enter fullscreen mode Exit fullscreen mode

The output of the code above will be

"Start task1, when done, start task2 "
"Task2 has started..."
Enter fullscreen mode Exit fullscreen mode

Importance of callback functions ?

We have learnt what a callback function is, but why do we need callback functions?

When performing a complex task, we break that task down into minor steps. To create a connection amongst these steps based on their order and time taken (optional), we use callback functions.

Use case of callback function

To better understand callback functions, let's imagine we run a fried rice street food business, where customers can order our delicious fried rice.

Callback function

Below we have outlined the steps to prepare our fried rice when the customer places an order

Steps in preparing fried rice

  • Customer orders fried rice
  • Preparation begins
    • Chop and prepare the ingredients ----- 3 seconds
    • Season the meat and prepare the egg and rice ------ 2 seconds
    • Mix the ingredients ------ 2 seconds
    • Add soy sauce, oyster sauce, and salt to taste ------ 1 second
  • Serve customer ----- 2 seconds

For our streetfood business to be profitable:

  • The customer first orders the fried rice, and then we begin the production.

Let's establish a relationship between the ordering and the preparing of the fried rice.

See the code below:

function customerOrders(call_back){
 console.log("Customer places order")
//call startProducing
 call_back()
}

function startProducing(){
console.log("Begin fried rice preparation")
}

//pass startProducing function to customerOrders
customerOrders(startProducing)
Enter fullscreen mode Exit fullscreen mode

In the code above:

  • We defined a customerOrders() function to indicate the customer has placed an order
  • We defined a startProducing() function to start preparing the fried rice
  • To establish a relationship between the ordering and preparation of the fried rice, we will use a callback function. Once, the order has been placed, then we will start the preparation
  • To achieve the above, we will pass the startProducing function as an argument to the customerOrders function.

The output of the code above will be :

"Customer places order"
"Begin fried rice preparation"
Enter fullscreen mode Exit fullscreen mode

Let's assume the customer is undecided, so he waits for 2 seconds before placing the order.After 2 seconds, the order will be placed, and we can start preparing our delicious fried rice.

Let's expand our code to accommodate the 2 seconds delay using the setTimout() function.

function customerOrders(call_back){
//wait for 2 seconds and runs the inner function
setTimeout(function(){
 console.log("Customer places order")
//call startProducing function
 call_back() 
},2000)

}

function startProducing(){
console.log("Begin fried rice preparation")
}

customerOrders(startProducing)
Enter fullscreen mode Exit fullscreen mode
  • We have still kept the relationship between ordering and preparation of the fried rice by passing the startProducing as the callback function to customerOrders.

We also introduce another aspect, the time it takes before the customer places an order.

  • The setTimeout() is another function we are nesting into the customerOrders() function.
  • When the customerOrders() function is executed, it will run the setTimeout function in the background
  • After two seconds delay, the function passed to the setTimeout will execute.

Note: In the body of that function, we finally run our startProducting function.
Because we are passing functions to other functions, and running functions in functions, we are effectively creating callback functions. The key takeaway is, there is a link between all these functions.

So far, we have used callback functions to perform to important steps:

  • Allow 2 seconds delay, and then customer places an order
  • Start preparing the fried rice, when customer places an order.

Now that the customer has placed the order, let's start preparing the fried rice.

We will insert all the specific tasks to be carried out in preparing the fried rice in our startProducing function. This is to form a connnection between each task.

Because each individual task takes some time to complete, we will use the setTimeout function to stimulate the time it takes to perform that task in the background.

After the task has been completed, we will inform the customer about the action that has been taken.

The snippet for the setTimeout function is as below:

setTimeout(function(){
    //code goes here
}, 2000); //Time before execution
Enter fullscreen mode Exit fullscreen mode

Let's expand our code to include the specific tasks to be performed.

  • For our first task, it will take 3 seconds to complete the process. After the duration has elapse, we will execute the code passed to the setTimeout
//customer places order
function customerOrders(call_back) {
  //wait for 2 seconds
  setTimeout(function () {
    console.log("Customer places order");
    //start preparing the fried rice.
    call_back();
  }, 2000);
}

//start fried rice preparation
function startProducing() {
  setTimeout(function () {
    console.log("Begin fried rice preparation");

    //delay for 3s and run the function
    setTimeout(function () {
      console.log("Chopped and prepared the ingredients");
    }, 3000);
  }, 0000);
}
//run the tasks
customerOrders(startProducing);

Enter fullscreen mode Exit fullscreen mode

The output will be:


"Customer places order"
"Begin fried rice preparation"
"Chopped and prepared the ingredients"

Enter fullscreen mode Exit fullscreen mode

Let's complete the preparation of our fried rice in the startProducing() function by merging each next step to take inside the previous step.This will help establish a link between each step.

The illustration for the code execution is as below:

Promise Illustration

The entire code is as below:

function customerOrders(call_back) {
  //customer wait for 2 seconds
  setTimeout(function () {
    console.log("Customer places order");
    //call startProducing function
    call_back();
  }, 2000);
}
// Start preparing the fried rice
function startProducing() {
//step 1
  setTimeout(function () {
    console.log("Begin fried rice preparation");
    //step 2
    setTimeout(function () {
      console.log("Chopped and prepared ingredients");
      // step 3
      setTimeout(function () {
        console.log("Seasoned the meat and have prepared the egg and rice");
        //step 4
        setTimeout(function () {
          console.log("Ingredients have been mixed");
          //step 5
          setTimeout(function () {
            console.log("Added soy sauce, oyster sauce, and salt to taste");
            //step 6
            setTimeout(function () {
              console.log("Serving the delicious fried rice");
            }, 2000);
          }, 1000);
        }, 4000);
      }, 2000);
    }, 3000);
  }, 0000);
}

customerOrders(startProducing);

Enter fullscreen mode Exit fullscreen mode

The output of the code will be :

"Customer places order"
"Begin fried rice preparation"
"Chopped and prepared ingredients"
"Seasoned the meat and have prepared the egg and rice"
"Ingredients have been mixed"
"Added soy sauce, oyster sauce, and salt to taste"
"Serving the delicious fried rice"
Enter fullscreen mode Exit fullscreen mode

We have learnt how to perform a sequence of tasks using callback functions.

We can use the same callback function technique,when fetching multiple data from an api. Where we rely on the result of the initial api call to perform the next action.

But this approach introduces another layer of complexity.

Let's explore further.

Welcome to callback hell

Eventhough we have been able to prepare our delicious fried rice, the code above looks messy. We needed to nest callback **into **callback **into **callback. This phenomenon is termed callback hell.

Callback hell is the situation where callbacks are nested within other callbacks several levels deep, potentially making it difficult to understand and maintain the code.

Callback hell

Can you spot the pyramid shape highlight in the yellow section below, and all the }) at the end of our program? This is graciously known as callback hell.

If we nest callbacks in such a way describe above, we are more likely to write, difficult to read, error prone and difficult to maintain code. The solution is to use Promise to handle each tasks.

In the next section, we dive into Promise

Introducing Promise

In an asychronous operation (tasks that takes sometime to complete), instead of arranging for a function to be called at a later time (callback function), you can return an object that represents a future value.

The return object is what we call Promise.This object represents the future completion (or failure) of an asynchronous operation and its resulting value.

In simple terms, a Promise is container for a future value. An example of a future value is a response from an AJAX call or fetching data from an API using the fetch() method.

A promise is an asynchronous action that may complete at some point and produce a value. It is able to notify anyone who is interested when its value is accessible. We can also define Promise, as an object that is used as a placeholder for the future result of an asynchronous operation.

Promise analogy

We can appreciate the concept of Promise using the analogy below:

  • When you place a bet on a football match, there is a *promise * that you will receive money if you guess the correct outcome of the game.
  • So you purchase the betting slip (promise) right away.
  • On match day, if you guess the correct outcome, you will receive the money,because it was promised.

Using our initial fried rice scenario,if a customer places an order for our delicious fried rice, we will give the customer something to hold on to (for instance a slip).
The slip is a promise to the customer we would get him our delicious fried rice, if he orders now.
With this assurance, he can wait for sometime as we prepare the fried rice.

We promise to attend to the customer in any case:

  • If we were able to prepare the fried rice, the customer is informed, and he can enjoy his fried rice
  • If something happened (perhaps, we did not have enough rice), and we couldn't prepare the fried rice, we will still notify the customer, and the customer goes home disappointed.

Advantages of Promise

  • Using Promise, we no longer need to rely on events and callbacks passed into asynchronous functions to handle asynchronous results.
  • Instead of nesting callbacks, we can chain promises for a sequence of asynchronous operations; escaping callback hell.

Creating a Promise

The syntax for creating a Promise is as below:

let success = true;

let promise = new Promise(function(resolve, reject) {
  // perform an asynchronous operation here...

//return state of the promise
  if (success) {
    resolve(value);
  } else {
    reject(error);
  }
});
Enter fullscreen mode Exit fullscreen mode

Let's understand the code above:

  • We call the new Promise() constructor and pass it a callback function as an argument
  • The callback passed to the new Promise() is the executor, and is responsible for performing an asynchronous operation.
  • The callback accept two parameters, resolve and reject
  • Inside the body of the callback, we perform any asynchronous operation such as fetching data from an API.
  • If the asynchronous operation successfully completes, and we get some kind of value, we will call the resolve() function and pass it the value.
  • If the asynchronous operation was unsuccessfull perhaps due some slow internet connectivity, we will call the reject() function, and pass it the error.

The returned value of new Promise() is an Object, this Object serves as a placeholder for value we will get back from the asynchronous operation.

Understanding the Promise Lifecycle

  • Whiles the asynchronous task in being carried, the Promise Object returned, will have a State property with an inital value of Pending
  • When the asynchronous task completes, the value of the State property updates to Settled
  • If the asynchronous task was successful, the State property updates to fulfilled
  • If the asynchronous task was unsuccessful, the State property updates to rejected
  • There is also a result property in the Promise object. It is initially set to undefined, then changes to the value we got from the asynchronous operation when resolve() is called or error when reject(error) is called.

Promise JavaScript

The examine that new Promise() constructor function returns a Promise Object

const promise = new Promise((resolve,reject)=>{

})

console.log(promise)
Enter fullscreen mode Exit fullscreen mode

The output of the code above will be

Promise JS

To sum up,the callback function we pass to the new Promise() constructor runs automatically and attempts to perform a task.** When it is done with the attempt, it calls resolve() if it was successful or reject() if there was an error.**

Callback

Examine the code below:

const getSomething = ()=> {
    return new Promise(function(resolve,reject){
// perform some async operation here...

let success = true  // Stimulate successful async operation

  if (success) { 
    resolve("data"); // pass the value you got from the async operation
  } else {
    reject(error); // pass the error if unsuccessful
  }
    })
}

console.log(getSomething())
Enter fullscreen mode Exit fullscreen mode
  • We declared the getSomething function, in the body of the function, we call the new Promise constructor function
  • We passed the callback function, and it the body of the callback, we perform the asynchronous operation.
  • If the operation was successful, we call the resolve() and pass it the data we got from the async operation.
  • If the operation was unsuccessful, we call the reject() and pass it the error we got from the async operation.

Below will be the result of the code above:

Promise fulfilled

Promise and the fetch API.

Inside of using the AJAX call to make a request for resources on a web server, the global fetch() method provides an easy and logical way to fetch resouces asynchronously across the network.

When the fetch() method is executed, it immediately returns a Promise Object , and goes through the Promise lifecyle as described above.

A basic fetch request is easy to set up:

const promise = fetch('http://www.example.com')
console.log(promise)
Enter fullscreen mode Exit fullscreen mode

The output will be

Promise Fetch

Consuming Promises using the then() method

We need to find a way of utilizing the Promise Object returned after the completion of the asynchronous task.The then() method will help with that

To get the value of a promise when it’s fulfilled, you call the then() method of the promise object.

The following shows the syntax of the then() method:

// consuming the promise object
promise.then(
  function(result) { /* handle a successful result */ },
  function(error) { /* handle an error */ }
);

Enter fullscreen mode Exit fullscreen mode
  • In the then() method, we accept two callback functions.
  • If the Promise State is fulfilled, we run the first function. In the function we accept one parameter, the result of the asynchronous task. We can now make use of the result.
  • If the Promise State is rejected, we run the second function,it also accept the error we got from the asynchronous task as a parameter.

Examine the code below:

const getSomething = ()=>{
  return new Promise((resolve,reject)=> {
// perform some async operation here...
 let success = true //async operation was successful
  if (success) {
    resolve("We got some data back");
  } else {
    reject("Oops Error fetching data");
  }
    })

  })
}

//get the value of the promise object using then()
getSomething().then(
function onFulfilled(result){
/* handle a successful result */
  console.log(result)
}, function onRejected(error){
/* handle an error*/
  console.log(error)
})
Enter fullscreen mode Exit fullscreen mode

Let's understand the code above:

  • We utilize the then() method to consume or make use of what we have in the Promise object.Whether the asynchronous operation was successful or not, we want to know and take some action.
  • The first argument of the .then() is a function that runs when the promise is fulfilled.
  • The second argument of the .then() is a function that runs when the promise is *rejected *
  • If we are interested in only the successful completion, we can insert only one callback function in the .then() method.
  • In the code above, we pass to the .then() method both the onFulfilled function and onRejected function.
  • In the body of the function, we can log the result or the error if any.

The output of the code above will be:

Fetch API

Another example using the fetch() method

const result = fetch('https://jsonplaceholder.typicode.com/todos/1')
  .then(response => response.json(), error => console.log(error) )

console.log(result)
Enter fullscreen mode Exit fullscreen mode

The output will be:

JavaScript Promize

Using the .catch() method to handle errors

If we are interested only in the error when the Promise is rejected, we can use the catch() method of the Promise object to "catch" the error

promise.catch((error) => {
  console.log(error);
});
Enter fullscreen mode Exit fullscreen mode

We can rewrite the code above as :

const getSomething = ()=> {
  return new Promise((resolve,reject)=> {
// perform some async operation here...
 let success = false //async operation was unsuccessful
  if (success) {
    resolve("We got some data back");
  } else {
    reject("Opps Error fetching data");
  }
    })


}

//get the value of the promise object using then()
getSomething().then(
function onFulfilled(result){
/* handle a successful result */
  console.log(result)
}).catch((error)=>{
/* handle a unsuccessful result */
  console.log(error)
})
Enter fullscreen mode Exit fullscreen mode

Using the fetch() method

const result = fetch('https://jsonplaceholder.typicode.com/todos/1')
  .then(response => response.json()).catch(error => console.log(error))

console.log(result)
Enter fullscreen mode Exit fullscreen mode

Promise Chaining

Often, we need to execute two or more related asynchronous tasks. When the previous task succeeds, we want to start the next task with the result of the previous task. Promise chaining help us to accomplish that.

Examine the code below:

const doSomething = ()=>{
  return new Promise((resolve,reject)=>{
    //stimulate asynchronous operation
    setTimeout(function(){
      resolve(10)
    },3000)
  })
}
//consume the returned promise object
const secondPromise = doSomething().then((result)=>{
  console.log(`Result is ${result}`)
  //dosomething else with the result and return the update Promise object
  return result*2
})

console.log('secondPromise Object',secondPromise)
Enter fullscreen mode Exit fullscreen mode
  • In the code above, we used the setTimeout function to stimulate an asynchronous task.
  • We wait for 3 seconds, and if the asynchronous task was successful, we call the resolve() method, and pass it a value of 10.
  • We get back a Promise Object with PromiseState and PromiseResult properties.
  • Since the operation was successful, we change the PromiseState property of the Promise object from Pending to fulfilled, and the PromiseResult to 10.
  • In the then()method we can make use of the Promise object, we implement a callback function have access to the PromiseResult and log it to the console.

What if we want to perform another task with the PromiseResult, how do we handle that ?

  • We can update the value in the PromiseResultand then return the Promise object.
//update the result and return the Promise object
return result * 2
Enter fullscreen mode Exit fullscreen mode

To examine that, the then() method can ** return updated Promise object,** we can assign the returned Promise object to the secondPromise variable.

Promise 2

  • From the above screenshot, the Promise Object was updated, and now has a PromiseResult of 20.
  • We can even update this result by chaining another .then() method or simply log the result to the console.

See the modified code below

const doSomething = ()=>{
  return new Promise((resolve,reject)=>{
    //stimulate asynchronous operation
    setTimeout(function(){
      resolve(10)
    },3000)
  })
}
//consume the promise object using promise chaining
doSomething().then((result)=>{
  console.log(`Result is ${result}`)
  //dosomething else with the result
  return result*2
}).then((nextResult)=> {
// then do something else again
  console.log(`The next operation resulted in ${nextResult}`)
})

Enter fullscreen mode Exit fullscreen mode

The output will be:

"Result is 10"
"The next operation resulted in 20"

Enter fullscreen mode Exit fullscreen mode

How did we arrive at the about output?

  • The first .then() method returned a Promise object, this Promise object was utilized by the second .then() method.
  • We can keep calling successive then() method whiles returning the Promise Object.

Essentially Promise chaining, is linking a series of then() method to define what needs to be done when the previous promise is fulfilled.

Basically, each promise represents the completion of another asynchronous step in the chain.

Promise chaining works this way:

  • If our first task is complete, *then * we do the other task, **then **we do the next task, **then **the next, **then **the next etc.

The below illustrates Promise chaining using the fetch() method

fetch('https://jsonplaceholder.typicode.com/todos/1')
  .then(response => response.json())
  .then(json => console.log(json))
Enter fullscreen mode Exit fullscreen mode
  • Our first then() returns a Promise Object with a fulfilled state, we can chain a new then() do something else with the result returned by the previous then()

In time past, performing several asynchronous operations in a row would lead to the classic callback pyramid of doom.

Solving the callback hell using Promise chaining

Let's now see how to use Promise to solve our Callback hell encounted in our Fried rice scenario

If the shop is open, the customer waits for 2 seconds and makes his order.

Below is how to illustrate this process using Promises:

const customerOrder = (time, task)=>{
      let isOpen = true;
  return new Promise((resolve,reject)=>{
    if(isOpen){
      setTimeout(()=>{
        //run first task if async operation is successful
        resolve(task())

      },time)
    }else {
      reject("shop is closed")
    }
  })

}
customerOrder(2000,()=>{console.log('Customer places order')})

Enter fullscreen mode Exit fullscreen mode
  • If the shop is open, the customer waits for 2 seconds and places the order
  • We pass two arguments to the customerOrder function
    • First is the duration it takes to complete the task
    • Second, the task to perform
  • The actual task will be performed in the resolve() method, doing this, we get access to the fulfilled Promise object

Now, instead of nesting callback into callback, we are going to use Promise chaining to define what needs to be done when the customer places an order.

Think of it as giving instructions to your cooks on what needs to be done after the customer places an order.

  • You tell the cook to "Do this task first, then **do the next task, **then **this other task, **then..., then..., then..." etc.

The entire process is captured below:

  • When the customerOrder() function is executed, it returns our Promise Object.
  • The Promise Object will indicate whether our promise is fulfilled or rejected.
  • If fulfilled, we run our tasks.
  • Each of our tasks will be in the then() method.
  • For each task, we will return our updated Promise Object.

The complete code is as below:

const customerOrder = (time, task) => {
  let isOpen = true;
  return new Promise((resolve, reject) => {
    if (isOpen) {
      setTimeout(() => {
        //run each task here if shop is open
        resolve(task());
      }, time);
    } else {
      reject("shop is closed");
    }
  });
};

customerOrder(2000, () => {
  console.log("Customer places order");
})
  // then task 1
  .then(() => {
    return customerOrder(0000, () => {
      console.log("Begin fried rice preparation");
    });
  })
  // then task 2
  .then(() => {
    return customerOrder(3000, () => {
      console.log("chopped and prepared the ingredient");
    });
  })
  // then task 3
  .then(() => {
    return customerOrder(2000, () => {
      console.log("seasoned the meat and prepared egg stew");
    });
  })
  // then task 4
  .then(() => {
    return customerOrder(4000, () => {
      console.log("Ingredients have been mixed");
    });
  })
  // then task 5
  .then(() => {
    return customerOrder(1000, () => {
      console.log("Added soy sauce, oyster sauce, and salt to taste");
    });
  })
  // then task 6
  .then(() => {
    return customerOrder(2000, () => {
      console.log("Serving the delicious fried rice");
    });
  })
  .finally(() => {
    console.log("Thank you for stopping by ");
  });

Enter fullscreen mode Exit fullscreen mode

The output will be:

"Customer places order"
"Begin fried rice preparation"
"chopped and prepared ingredient"
"seasoned the meat and prepared egg stew"
"Ingredients have been mixed"
"Added soy sauce, oyster sauce, and salt to taste"
"Serving the delicious fried rice"
"Thank you for stopping by "
Enter fullscreen mode Exit fullscreen mode

The .finally() method

Occasionally you want to execute similar function whether the promise is fulfilled or rejected.
Examine the code below:

const doSomething = ()=>{
  // let run some task here
}

const getSomething = ()=> {
  return new Promise((resolve,reject)=> {
// perform some async operation here...
 let success = false //async operation was unsuccessful
  if (success) {
    resolve("We got some data back");
  } else {
    reject("Opps Error fetching data");
  }
    })


}

//get the value of the promise object using then()
getSomething().then(
(result)=> {
  /* handle a successful result */
  console.log(result)
// call doSomething
  doSomething()
}
).catch((error)=>{
/*handle an unsuccessful task */
  console.log(error)
// call doSomething
  doSomething()
})
Enter fullscreen mode Exit fullscreen mode

Notice that, doSomething() function is duplicated in the both the then() method and catch() method.

To take out this duplication and execute the doSomething() whether the promise is fulfilled or rejected, you use the finally() method

The entire code will be :


const doSomething = ()=>{
  // let run some task here
}
const getSomething = ()=> {
  return new Promise((resolve,reject)=> {
// perform some async operation here...
 let success = false //async operation was unsuccessful
  if (success) {
    resolve("We got some data back");
  } else {
    reject("Opps Error fetching data");
  }
    })


}

//get the value of the promise object using then()
getSomething().then(
(result)=> {
  /* handle a successful result */
  console.log(result)

}
).catch((error)=>{
/*handle an unsuccessful task */
  console.log(error)

}).finally(()=>{
//finally, run this function
  doSomething()
})
Enter fullscreen mode Exit fullscreen mode

Summary

  • We can define Promise, as an object that is used as a placeholder for the future result of an asynchronous operation
  • A promise begins in the Pending state and ends in either fulfilled or rejected state.
  • Utilize the then() method to arrange a callback to be executed when the promise is fulfilled, and catch() method to handle a callback to be executed when the promise is rejected.
  • Place the task to be execute in the finally() method whether the promise is fulfilled or rejected.

If you have found value in this article, kindly share on your social media platform.
Do you need some clarification or you have a suggestion? Feel free to post your comment, I would love to read from you.
Don't forget to follow me on Twitter

Top comments (2)

Collapse
 
fruntend profile image
fruntend

Π‘ongratulations πŸ₯³! Your article hit the top posts for the week - dev.to/fruntend/top-10-posts-for-f...
Keep it up πŸ‘

Collapse
 
efkumah profile image
Emmanuel Fordjour Kumah

Grateful, thanks for your support. I Appreciate it

Take Your Github Repository To The Next Level

Take Your Github Repository To The Next Level πŸš€οΈ: A step-by-step guide on creating the perfect Github repository.