DEV Community

Cover image for What is Promise in Javascript with real world example
Hoang Manh Cam
Hoang Manh Cam

Posted on

What is Promise in Javascript with real world example

Introduction to Promise in JavaScript

Asynchronous programming is a fundamental aspect of JavaScript, as it allows for non-blocking operations and enhances the overall performance of web applications. However, handling asynchronous operations can become complex and lead to “callback hell” or nested callbacks. To address this issue and provide a more structured approach, Promises were introduced in JavaScript.

Promises are a built-in feature introduced in ECMAScript 2015 (ES6) that represent the eventual completion (or failure) of an asynchronous operation. They provide a way to write cleaner, more readable, and maintainable asynchronous code by simplifying the handling of asynchronous operations and their results.

At its core, a Promise is an object that can be in one of three states: pending, fulfilled, or rejected.

  • Pending: The initial state when a Promise is created. It represents an ongoing, unresolved asynchronous operation.
  • Fulfilled: The state when the asynchronous operation is successfully completed. The Promise is fulfilled, meaning it completed successfully, and it holds a value.
  • Rejected: The state when an error occurs during the asynchronous operation. The Promise is rejected, indicating that an error occurred, and it holds a reason or an error object.

Promise with three states

The key concept behind Promises is the ability to attach callbacks to handle the fulfillment or rejection of the asynchronous operation. These callbacks are registered using the .then() method on a Promise instance. The .then()method takes two optional arguments: a callback function to handle the fulfilled state and a callback function to handle the rejected state.

Here’s an example that demonstrates the basic usage of a Promise:

const myPromise = new Promise((resolve, reject) => {
  // Asynchronous operation
  setTimeout(() => {
    const data = "Promise resolved successfully!";
    resolve(data); // Fulfill the Promise
    // Or reject the Promise
    // reject(new Error("An error occurred")); 
  }, 2000);
});

myPromise
  .then(result => {
    console.log("Promise fulfilled:", result);
  })
  .catch(error => {
    console.error("Promise rejected:", error);
  });
Enter fullscreen mode Exit fullscreen mode

In this example, we create a new Promise using the Promise constructor. Inside the Promise’s executor function, we perform an asynchronous operation, which in this case is simulated using setTimeout(). After the operation completes successfully, we call resolve() with the data we want to pass as the fulfillment value.

We chain a .then() method to the Promise instance to register a fulfillment callback that will be called when the Promise is fulfilled. Inside the callback, we log the fulfillment value to the console.

If the Promise is rejected, either due to an error or by explicitly calling reject(), the rejection callback specified using .catch() will be invoked, and we can handle the error accordingly.

Promises provide a more structured and readable way to handle asynchronous operations and their outcomes in JavaScript. They allow you to write asynchronous code that resembles synchronous code, making it easier to reason about and maintain. Promises also offer additional features like chaining, error propagation, and advanced composition using Promise.all() and Promise.race(), which further enhance their utility in modern JavaScript development.

How Promise works under the hood

Promises are an integral part of JavaScript’s asynchronous programming model and provide a way to handle asynchronous operations in a more structured and manageable manner. To understand how Promises work under the hood, let’s delve into their inner workings.

Promise Creation

When you create a Promise using the new Promise() syntax, you provide an executor function as its argument. This executor function is called immediately when the Promise is created. It receives two callback functions as parameters: resolve and reject. Inside the executor function, you perform your asynchronous operation and call either resolve(value) to fulfill the Promise or reject(reason) to reject it.

const myPromise = new Promise((resolve, reject) => {
  // Asynchronous operation
  setTimeout(() => {
    const data = "Some result";
    resolve(data); // Fulfill the Promise
    // Or
    // reject(new Error("An error occurred")); // Reject the Promise
  }, 2000);
});
Enter fullscreen mode Exit fullscreen mode

Promise States

Promises have three possible states:

  • Pending: The initial state when the Promise is created. It is neither fulfilled nor rejected.
  • Fulfilled: The state when the asynchronous operation is successfully completed. The Promise is fulfilled, and it holds a value.
  • Rejected: The state when an error occurs during the asynchronous operation. The Promise is rejected, and it holds a reason or error object.

The Promise starts in the pending state and transitions to either the fulfilled or rejected state based on the outcome of the asynchronous operation.

Chaining Promises

One of the key features of Promises is the ability to chain them together, enabling sequential execution of asynchronous operations and avoiding callback hell. To chain Promises, you use the .then() method, which takes a callback function as its argument. This callback function receives the resolved value from the previous Promise and can return another Promise or a value. The .then() method returns a new Promise, allowing you to chain multiple .then() calls together.

myPromise
  .then(result => {
    console.log("First operation result:", result);
    return anotherAsyncOperation();
  })
  .then(anotherResult => {
    console.log("Second operation result:", anotherResult);
    // Perform additional operations or return a value
  })
  .catch(error => {
    console.error("An error occurred:", error);
  });
Enter fullscreen mode Exit fullscreen mode

In this example, the first .then() callback receives the resolved value from myPromise and performs an additional asynchronous operation. It returns another Promise, which becomes the input for the second .then() callback.

Asynchronous Execution

Promises utilize an event-driven mechanism and leverage the JavaScript event loop to execute asynchronous operations. When a Promise is created, the executor function is called immediately, and the asynchronous operation starts. The Promise registers callbacks internally and allows other synchronous operations to continue while it awaits the completion of the asynchronous task.

Resolution and Rejection

Once the asynchronous operation completes, the Promise transitions to the fulfilled state if it was successful and the resolve function was called. The resolved value is then passed to the corresponding .then() callback for further processing.

If an error occurs during the asynchronous operation and the reject function is called, the Promise transitions to the rejected state. The reason or error object passed to reject is propagated to the nearest .catch() block or the subsequent .catch() callbacks in the Promise chain.

Error Handling

Promises provide built-in error handling through the .catch() method. By chaining a .catch() call at the end of the Promise chain, you can capture and handle any errors that occurred in any of the preceding Promises. If any Promise in the chain is rejected, the control flow skips to the nearest .catch() block, allowing you to handle the error gracefully.

Promises internally manage the propagation of errors through the chain, ensuring that any unhandled rejections are caught and reported.

Promise.all() and Promise.race()

Promises offer utility methods like Promise.all() and Promise.race() to handle multiple Promises simultaneously.

  • Promise.all() takes an array of Promises as its argument and returns a new Promise that resolves to an array containing the resolved values of all the Promises. It waits for all Promises in the array to settle, and if any Promise rejects, the entire Promise.all() operation is rejected.
  • Promise.race() takes an array of Promises and returns a new Promise that settles as soon as any of the Promises in the array settles (fulfills or rejects). It resolves or rejects with the value or reason of the first settled Promise.

These methods are useful for handling scenarios where you need to wait for multiple asynchronous operations to complete concurrently or track the completion of the fastest operation.

Overall, Promises provide a structured and powerful mechanism to handle asynchronous operations in JavaScript. They allow for cleaner and more readable code by enabling the chaining of asynchronous operations and offering built-in error handling capabilities.

Example using Promise in real world scenario

Consider a weather application that fetches weather data from an API. Let’s create a function getWeatherData() that uses Promises to handle the asynchronous fetch operation:

function getWeatherData() {
  return new Promise((resolve, reject) => {
    const apiKey = 'your-api-key';
    const apiUrl = `https://api.weatherapi.com/v1/current.json?key=${apiKey}&q=London`;

    fetch(apiUrl)
      .then(response => {
        if (response.ok) {
          return response.json();
        } else {
          throw new Error('Unable to fetch weather data.');
        }
      })
      .then(data => {
        resolve(data);
      })
      .catch(error => {
        reject(error);
      });
  });
}
Enter fullscreen mode Exit fullscreen mode

In this example, we use the fetch() API to make an HTTP request to retrieve the current weather data for London. The fetch() function returns a Promise that resolves to the Response object.

Inside the Promise’s executor function, we handle the asynchronous operation using the .then() method. If the response is successful (response.ok is true), we parse the JSON data and resolve the Promise with the resulting data. If there is an error, we throw a new Error object and reject the Promise.

Now, let’s use the getWeatherData() function and chain Promises to handle the weather data retrieval and display:

getWeatherData()
  .then(data => {
    const weather = data.current;
    console.log(`Temperature in ${data.location.name}: ${weather.temp_c}°C`);
    console.log(`Condition: ${weather.condition.text}`);
  })
  .catch(error => {
    console.error('Error fetching weather data:', error);
  });
Enter fullscreen mode Exit fullscreen mode

In this example, we call the getWeatherData() function and chain a .then() callback to handle the resolved data. We extract the necessary information from the current property of the data object and log the temperature and weather condition to the console.

If there is an error during the retrieval or parsing of the weather data, the .catch() callback will be triggered, and we log the error message to the console.

By using Promises, we can handle the asynchronous nature of the weather data retrieval, ensure proper error handling, and create a more readable and manageable code flow.

Note: In a real-world application, you would typically handle more complex scenarios, such as handling multiple API requests concurrently using Promise.all(), transforming and combining data from different Promises, or incorporating async/await syntax for even cleaner asynchronous code.

How to Handle Multiple Promises

Multiple Promises

Handling multiple Promises in JavaScript can be done using Promise.all() or Promise.race(). These methods allow you to work with multiple Promises concurrently and handle their results in different ways. Here’s how you can use them:

Promise.all()

The Promise.all() method takes an array of Promises as its argument and returns a new Promise that resolves when all the Promises in the array have resolved successfully. If any of the Promises in the array is rejected, the entire Promise.all() operation is rejected. The resolved values of the individual Promises are collected into an array in the same order as the original array.
Here’s an example:

const promise1 = new Promise(resolve => setTimeout(() => resolve('Promise 1'), 2000));
const promise2 = new Promise(resolve => setTimeout(() => resolve('Promise 2'), 3000));
const promise3 = new Promise(resolve => setTimeout(() => resolve('Promise 3'), 1000));

Promise.all([promise1, promise2, promise3])
  .then(results => {
    console.log('All Promises resolved:', results);
  })
  .catch(error => {
    console.error('At least one Promise rejected:', error);
  });
Enter fullscreen mode Exit fullscreen mode

In this example, we create three Promises that resolve after different delay times using setTimeout(). We pass an array containing these Promises to Promise.all(). The .then() callback is called when all the Promises have resolved, and the resolved values are logged to the console. If any of the Promises is rejected, the .catch() callback is triggered.

Promise.race()

The Promise.race() method takes an array of Promises and returns a new Promise that settles (resolves or rejects) as soon as any of the Promises in the array settles. It resolves with the value of the first Promise that resolves or rejects with the reason of the first Promise that rejects.
Here’s an example:

const promise2 = new Promise(resolve => setTimeout(() => resolve('Promise 2'), 3000));
const promise3 = new Promise((resolve, reject) => setTimeout(() => reject(new Error('Promise 3 Error')), 1000));

Promise.race([promise1, promise2, promise3])
  .then(result => {
    console.log('First Promise resolved:', result);
  })
  .catch(error => {
    console.error('First Promise rejected:', error);
  });
Enter fullscreen mode Exit fullscreen mode

In this example, we have three Promises again with different delay times. However, this time, we use Promise.race() instead of Promise.all(). The .then() callback is called with the result of the first Promise that resolves or the .catch() callback is triggered with the error of the first Promise that rejects.

By using Promise.all() and Promise.race(), you can effectively handle multiple Promises and control the flow based on the collective results or the first settled Promise. These methods are especially useful when you need to wait for multiple asynchronous operations to complete concurrently or track the completion of the fastest operation.

Top comments (0)