Skip to main content

Command Palette

Search for a command to run...

Understanding Promises in JavaScript

Updated
4 min read

As JavaScript applications grew more complex, developers needed a better way to handle asynchronous operations. Earlier, callbacks were widely used for this purpose, but they often led to deeply nested code that was difficult to read and maintain.

To improve this situation, JavaScript introduced Promises. Promises provide a structured and readable way to manage asynchronous operations and handle their results.

In this article, we will cover:

  • What problem promises solve

  • Promise states (pending, fulfilled, rejected)

  • The basic promise lifecycle

  • Handling success and failure

  • The concept of promise chaining


The Problem with Callbacks

Before promises, asynchronous code was often written using callbacks.

Example:

getUser(function(user) {
  getOrders(user.id, function(orders) {
    getOrderDetails(orders[0], function(details) {
      console.log(details);
    });
  });
});

This structure creates deeply nested code that becomes harder to understand and maintain as the application grows.

This situation is sometimes referred to as callback nesting.

Promises were introduced to make asynchronous code cleaner and easier to manage.


What Is a Promise?

A promise represents a value that may be available now, later, or never.

In other words, a promise is an object that represents the future result of an asynchronous operation.

Example:

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

  if (success) {
    resolve("Operation successful");
  } else {
    reject("Operation failed");
  }
});

The promise will eventually produce either a successful result or an error.


Promise States

A promise can exist in one of three states.

Pending

The promise has been created but the result is not yet available.

Pending → Operation still in progress

Fulfilled

The operation completed successfully and produced a value.

Fulfilled → Promise resolved successfully

Rejected

The operation failed and returned an error.

Rejected → Promise failed

Visual representation:

Pending
   ↓
Fulfilled  (success)
   or
Rejected   (error)

Once a promise is fulfilled or rejected, its state cannot change.


Basic Promise Lifecycle

Let us look at a simple example.

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Data received");
  }, 2000);
});

Here:

  1. A promise is created.

  2. It starts in the pending state.

  3. After 2 seconds, resolve() is called.

  4. The promise becomes fulfilled.


Handling Success and Failure

Promises provide methods to handle both success and failure.

Handling Success with .then()

myPromise.then(result => {
  console.log(result);
});

Handling Errors with .catch()

myPromise
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error(error);
  });

This structure separates success handling from error handling, making code easier to understand.


Promise Chaining

One of the most powerful features of promises is chaining.

Promise chaining allows multiple asynchronous operations to run sequentially.

Example:

fetchUser()
  .then(user => {
    return fetchOrders(user.id);
  })
  .then(orders => {
    return fetchOrderDetails(orders[0]);
  })
  .then(details => {
    console.log(details);
  })
  .catch(error => {
    console.error(error);
  });

Instead of nesting callbacks, each step returns a promise that can be handled in the next .then().

This keeps the code structured and readable.


Promises vs Callbacks

Feature Callbacks Promises
Code structure Often nested Linear and readable
Error handling Harder to manage Centralized with .catch()
Readability Decreases with complexity Easier to follow
Chaining operations Difficult Built-in support

Promises significantly improve the readability and maintainability of asynchronous code.


Real-World Example

Fetching data from an API is a common use case for promises.

fetch("https://api.example.com/data")
  .then(response => response.json())
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error("Error:", error);
  });

Here:

  • The request returns a promise

  • .then() handles the successful response

  • .catch() handles any errors


Conclusion

Promises are a fundamental part of modern JavaScript and provide a better way to handle asynchronous operations compared to traditional callbacks.

Key points to remember:

  • Promises represent the future result of an asynchronous operation

  • They have three states: pending, fulfilled, and rejected

  • .then() handles successful results

  • .catch() handles errors

  • Promise chaining allows multiple asynchronous tasks to run in sequence

Understanding promises is an important step toward mastering asynchronous JavaScript and working effectively with modern APIs and applications.