DEV Community

Cover image for Promise in JavaScript
Sarthak Chatterjee
Sarthak Chatterjee

Posted on • Edited on

Promise in JavaScript

Introduction

According to MDN documentation "The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value."
You may think that what is written over here. Yeah! It is really hard to understand specially for someone who is pretty new at JavaScript programming, web development and stuffs like that. But now let's understand the Promise together in the simplest way.

Concept of Promise in the simplest way: Imagine, Promise is a magical box that you send on a mission to fetch a toy from a toy store. The box will bring back the toy you want, but it may take some time because the store is far away.

While the box is on its way to the store, you can do other things, like playing with your other toys, instead of just waiting for the box. Once the box returns, it will either have the toy you wanted (success) or tell you that it couldn't find it (failure).

So, a Promise is like a magical box that helps you get things done while it's working on your request, and when it's done, it tells you if it succeeded or not.

Now I am sure, if you read the MDN definition again you will get atleast an idea about promise. It's time to dive deeper into it.

What is Promise?

Promise is used to handle async operations in JavaScript. Now what is an async operation?
In JavaScript, an async operation is a non-blocking operation that allows the program to perform tasks asynchronously, ensuring that the main execution thread remains responsive. Here's a short example of an async operation using the modern async/await syntax:

// Creating an async operation that resolves after a short delay
function createAsyncOperation() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('Async operation complete!');
    }, 2000); // 2 seconds delay
  });
}

// An async function to handle the async operation
async function doAsyncTask() {
  console.log('Start of async task');
  const result = await createAsyncOperation();
  console.log(result);
  console.log('End of async task');
}

doAsyncTask(); 

Enter fullscreen mode Exit fullscreen mode

Don't get confused by this code, I will explain everything.
In this example, the createeAsyncOperation() function returns a Promise that resolves with the message "Async operation complete!" after a 2-second delay. The doAsyncTask() function uses the await keyword to pause its execution until the promise returned by createAsyncOperation() is resolved. The message is then logged to the console.
When you run this code, you'll see the following output:

Start of async task
(2-second delay)
Async operation complete!
End of async task
Enter fullscreen mode Exit fullscreen mode

Notice that during the 2-second delay, other code (if any) could continue to execute, and the main thread remains responsive. This is the concept of asynchronous operations in JavaScript.
So Promise is used to handle these types of async operations in JavaScript.

  • Promise is a part of the ES6 (ECMAScript 2015) standard and provides a cleaner way to work with asynchronous code compared to traditional callback-based approaches.

  • It is a built-in JavaScript object that represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.

The Promise object has three possible states:

1. Pending: The initial state when the Promise is created and the asynchronous operation is still ongoing.

2. Fulfilled: The state when the asynchronous operation is successfully completed, and the Promise has a resolved value.

3. Rejected: The state when the asynchronous operation encounters an error or fails, and the Promise has a reason for rejection.

When to use it?

In JavaScript, you should use a Promise when dealing with asynchronous operations, such as network requests, file operations, or any task that may take some time to complete. Promises offer a clean and structured approach to manage asynchronous code, making it easier to handle callbacks and avoid the pitfalls of callback hell.
A Promise is used to wrap an asynchronous operation and allows you to handle its outcome using then() and catch() methods. When the operation completes, the Promise either calls the resolve() function (for success) or the reject() function (for failure) to transition to the appropriate state.

How to use it?

Suppose we have cart of shopping items.

 const cart = ['item1', 'item2', 'item3'];
Enter fullscreen mode Exit fullscreen mode

We have three functions, createOrder(), orderId() and proceedToPayment().
First let us see the call back approach for a simple shopping system starting from cart to a successful payment:

// Cart array
const cart = ['item1', 'item2', 'item3'];
// Imagine all functions are defined somewhere
// Using callbacks to handle the async operations
createOrder((order) => {
  orderId(order, (orderId) => {
    console.log('Order ID:', orderId);
    proceedToPayment(orderId, (paymentStatus) => {
      console.log(paymentStatus);
    });
  });
});

Enter fullscreen mode Exit fullscreen mode

With out promise we can face the callback hell, inversion of control.

  • Callback Hell : Callback hell refers to the situation in asynchronous JavaScript programming where multiple nested callbacks make the code difficult to read, maintain, and understand. This happens when one asynchronous operation depends on the result of another, leading to deeply nested functions.

Example of Callback Hell:

asyncFunction1((result1) => {
  asyncFunction2(result1, (result2) => {
    asyncFunction3(result2, (result3) => {
      // More nested callbacks...
    });
  });
});

Enter fullscreen mode Exit fullscreen mode
  • Inversion of Control : Inversion of Control (IoC) in asynchronous programming means letting external functions or libraries control when certain parts of your code execute. Callbacks are a basic form of IoC where you hand over control to functions you pass as callbacks.

  • By using Promises or async/await, the flow of execution is controlled by the Promise library or the JavaScript runtime, respectively, which allows you to write asynchronous code in a more sequential and readable manner. This helps avoid callback hell and leads to more maintainable and easier-to-understand code.
    Now we will see the promise approach of the same code we've seen (shopping) :

// Cart array
const cart = ['item1', 'item2', 'item3'];

// Using Promises to handle the async operations
createOrder()
  .then((order) => orderId(order))
  .then((orderId) => {
    console.log('Order ID:', orderId);
    return proceedToPayment(orderId);
  })
  .then((paymentStatus) => {
    console.log(paymentStatus);
  })
  .catch((error) => {
    console.error('Error occurred:', error);
  });

Enter fullscreen mode Exit fullscreen mode

In this example, we've refactored the callback-based code into Promise-based code. The createOrder(), orderId(), and proceedToPayment() functions now return Promises, allowing us to chain them together using then() to handle the results of each async operation. If any error occurs at any stage, the catch() method will catch and handle it. This approach makes the code more readable and avoids callback hell.

Creating a Promise

Remember the "Magical box"?
Creating a Promise in JavaScript is like making a promise to do something and giving the magical box to someone else to fulfill that promise. The magical box represents the Promise object, and it can be in three states: pending (waiting to fulfill the promise), fulfilled (successfully completed the promise), or rejected (couldn't fulfill the promise).

To create a Promise, you use the new Promise() constructor and pass a function with two parameters: resolve and reject. Inside this function, you do the task you promised, and then you call resolve() when it's successful or reject() when it's not.

Here's a simple example:

const magicBox = new Promise((resolve, reject) => {
  // Do something (e.g., fetching data from a server)
  // If successful, call resolve with the result
  // If there's an error, call reject with the error
});
Enter fullscreen mode Exit fullscreen mode

Once you create the Promise, you can use .then() to handle the successful result and .catch() to handle the error:

magicBox
  .then((result) => {
    // Handle the successful result
  })
  .catch((error) => {
    // Handle the error
  });
Enter fullscreen mode Exit fullscreen mode

JavaScript functions that return Promises

Functions are designed to perform tasks that may take some time, such as reading files, making network requests, or interacting with databases return Promise. By returning a Promise, these functions allow you to work with the results of the asynchronous task once it is completed, using then() and catch() methods.
Here are some examples of functions that return Promises:

  1. Fetching data from a server using fetch() (a built-in function that returns a Promise).
  2. Making an HTTP request using a library like Axios (Axios itself returns a Promise).
  3. In MongoDB, using Promises can be particularly useful when dealing with asynchronous operations, such as querying the database, inserting data, or updating records. MongoDB itself doesn't return Promises by default, but many modern MongoDB drivers, like Mongoose, support Promises natively or can be easily wrapped in Promises for better handling of asynchronous operations.

Summary

In simple words, a Promise in JavaScript is a way to handle tasks that take some time, like fetching data from the internet. It's like making a promise to get the data, and when the data is ready, the Promise will let you know if it succeeded or if there was a problem. Promises help you keep your code organized and avoid getting stuck waiting for tasks to finish.

Top comments (0)