JavaScript Promises

JavaScript Promises provide a way to handle the results of asynchronous operations. They allow you to write code that responds when an operation succeeds or fails, such as fetching data from an API, loading information from a server, or completing a background task.

Before Promises became widely used, developers often relied on nested callback functions to manage asynchronous operations. Promises provide a more structured approach to handling results and errors, especially when multiple operations depend on one another.

In this tutorial, you will learn what a Promise is, understand its states, create Promises, use resolve() and reject(), handle results with .then() and .catch(), execute cleanup with .finally(), and chain multiple Promises together.

What Is a Promise in JavaScript?

A Promise is a JavaScript object that represents the eventual completion or failure of an operation.

A Promise allows you to attach handlers that execute when the operation succeeds or fails. It is particularly useful when working with asynchronous tasks such as network requests, timers, and file operations.

For example, imagine an application that requests user information from a server. The response may arrive later, or the request may fail. A Promise provides a way to handle either outcome.

Example of a Promise

const myPromise = new Promise(function (resolve, reject) {
  resolve("Operation completed successfully");
});

myPromise.then(function (result) {
  console.log(result);
});

Output:

Operation completed successfully

In this example:

  • new Promise() creates a Promise object.
  • The executor function receives resolve and reject functions.
  • resolve() fulfills the Promise with a result.
  • .then() registers a handler for the fulfilled result.

The executor function runs immediately when the Promise is created. However, .then() handlers execute asynchronously as Promise reactions.

Promise States in JavaScript

A Promise has three possible states: pending, fulfilled, and rejected.

<box gap={3}> <row align=start gap={3}> <box background=”#FFF3CD” radius=”lg” padding={3} align=center justify=center minWidth=”104px”> <icon name=”clock” size=”xl” color=”#8A5A00″ /> <text color=”#8A5A00″ weight=”medium”>Pending</text> </box> <box flex=”1″ gap={1}> **Pending**

  The operation has not yet settled. The Promise has not been fulfilled or rejected.
</box>

</row> <row align=start gap={3}> <box background=”#DDF5E5″ radius=”lg” padding={3} align=center justify=center minWidth=”104px”> <icon name=”check-circle” size=”xl” color=”#176B3A” /> <text color=”#176B3A” weight=”medium”>Fulfilled</text> </box> <box flex=”1″ gap={1}> **Fulfilled**

  The operation completed successfully, and the Promise has a fulfillment value.
</box>

</row> <row align=start gap={3}> <box background=”#FCE4E4″ radius=”lg” padding={3} align=center justify=center minWidth=”104px”> <icon name=”x-circle” size=”xl” color=”#A61B1B” /> <text color=”#A61B1B” weight=”medium”>Rejected</text> </box> <box flex=”1″ gap={1}> **Rejected**

  The operation failed, and the Promise has a rejection reason, often an error.
</box>

</row> </box>

A Promise is settled when it is either fulfilled or rejected. Once settled, its state and outcome cannot change.

Example of Promise States

const promise = new Promise(function (resolve, reject) {
  resolve("Success");
});

promise.then(function (result) {
  console.log(result);
});

Output:

Success

The Promise is fulfilled with the value "Success".

If a Promise is rejected instead, its rejection handler can process the error.

const promise = new Promise(function (resolve, reject) {
  reject("Something went wrong");
});

promise.catch(function (error) {
  console.log(error);
});

Output:

Something went wrong

Creating Promises in JavaScript

You can create a Promise using the Promise constructor.

The constructor accepts an executor function that receives two arguments: resolve and reject.

  • resolve() fulfills the Promise with a value, unless the Promise has already been settled or the value causes it to adopt another Promise’s outcome.
  • reject() rejects the Promise with a reason.

Syntax

const promise = new Promise(function (resolve, reject) {
  // Perform an operation

  if (operationSucceeded) {
    resolve("Success");
  } else {
    reject(new Error("Operation failed"));
  }
});

The variable operationSucceeded represents a condition in this illustrative example. In actual code, replace it with a real condition.

Example of Creating a Promise

const orderPromise = new Promise(function (resolve, reject) {
  const orderPlaced = true;

  if (orderPlaced) {
    resolve("Order placed successfully");
  } else {
    reject(new Error("Order could not be placed"));
  }
});

orderPromise
  .then(function (message) {
    console.log(message);
  })
  .catch(function (error) {
    console.log(error.message);
  });

Output:

Order placed successfully

Because orderPlaced is true, the Promise is fulfilled and the .then() handler receives the success message.

If the condition were false, the Promise would be rejected, and the .catch() handler would display the error message.

The resolve() Function

The resolve() function fulfills a Promise with a value or causes it to adopt the state of another Promise or thenable.

It is commonly used when an asynchronous operation completes successfully.

Example of resolve()

const promise = new Promise(function (resolve) {
  resolve("Data loaded successfully");
});

promise.then(function (result) {
  console.log(result);
});

Output:

Data loaded successfully

When resolve() is called with a regular string, the Promise becomes fulfilled with that string as its value.

Using resolve() with a Number

const numberPromise = new Promise(function (resolve) {
  resolve(100);
});

numberPromise.then(function (value) {
  console.log(value * 2);
});

Output:

200

The .then() handler receives the value 100 and multiplies it by two.

Important Point About resolve()

Calling resolve() does not mean that every value is stored directly as the fulfillment value. If you pass another Promise or a thenable, the new Promise adopts its eventual outcome.

Also, once a Promise has been settled, subsequent calls to resolve() or reject() do not change its outcome.

The reject() Function

The reject() function rejects a Promise with a specified reason. The reason is often an Error object that describes what went wrong.

A rejected Promise can be handled with .catch() or the rejection callback provided to .then().

Example of reject()

const promise = new Promise(function (resolve, reject) {
  reject(new Error("Unable to load data"));
});

promise.catch(function (error) {
  console.log(error.message);
});

Output:

Unable to load data

The reject() function rejects the Promise. The .catch() handler receives the rejection reason, and error.message displays the error’s message.

Example of Handling a Failed Operation

function checkPayment(paymentSuccessful) {
  return new Promise(function (resolve, reject) {
    if (paymentSuccessful) {
      resolve("Payment completed");
    } else {
      reject(new Error("Payment failed"));
    }
  });
}

checkPayment(false)
  .then(function (message) {
    console.log(message);
  })
  .catch(function (error) {
    console.log(error.message);
  });

Output:

Payment failed

The function returns a Promise that fulfills or rejects according to the supplied condition.

This is a simplified demonstration. Real payment systems must verify payment status using a trusted server or payment provider rather than relying on a client-side boolean.

The .then() Method

The .then() method registers handlers for a Promise’s fulfillment and optionally its rejection.

It returns a new Promise, allowing you to transform results and connect multiple asynchronous operations.

Syntax

promise.then(onFulfilled, onRejected);
  • onFulfilled runs when the Promise is fulfilled.
  • onRejected runs when the Promise is rejected.
  • Either handler can be omitted.

Example of .then()

const promise = Promise.resolve("Hello, JavaScript!");

promise.then(function (message) {
  console.log(message);
});

Output:

Hello, JavaScript!

Promise.resolve() creates a fulfilled Promise for a regular value. The .then() handler receives that value.

Transforming a Promise Result

Promise.resolve(10)
  .then(function (number) {
    return number * 2;
  })
  .then(function (result) {
    console.log(result);
  });

Output:

20

The first .then() receives 10 and returns 20. The next .then() receives the returned value.

Returning a value from a handler fulfills the next Promise with that value. Returning a Promise makes the next Promise adopt its outcome.

The .catch() Method

The .catch() method registers a rejection handler for a Promise chain.

It is commonly used to handle errors from a rejected Promise or errors thrown by earlier handlers in the chain.

Syntax

promise.catch(onRejected);

The rejection handler receives the reason for the failure.

Example of .catch()

const promise = Promise.reject(
  new Error("Something went wrong")
);

promise.catch(function (error) {
  console.log(error.message);
});

Output:

Something went wrong

The Promise is rejected, so the .catch() handler processes the error.

Handling Errors in a Promise Chain

Promise.resolve(10)
  .then(function (number) {
    if (number < 20) {
      throw new Error("Number is too small");
    }

    return number;
  })
  .then(function (result) {
    console.log(result);
  })
  .catch(function (error) {
    console.log("Error:", error.message);
  });

Output:

Error: Number is too small

The first .then() throws an error. This rejects the Promise returned by that handler, so the next fulfillment handler is skipped and the .catch() handler receives the error.

A .catch() handler can also recover from an error by returning a regular value. If it does, the next Promise in the chain is fulfilled with that value.

The .finally() Method

The .finally() method registers a callback that runs when a Promise settles, whether it fulfills or rejects.

It is useful for cleanup tasks, such as hiding a loading indicator or restoring a button after an operation completes.

Syntax

promise.finally(onFinally);

The callback normally receives no fulfillment value or rejection reason.

Example of .finally()

Promise.resolve("Data loaded")
  .then(function (result) {
    console.log(result);
  })
  .finally(function () {
    console.log("Operation finished");
  });

Output:

Data loaded
Operation finished

The .finally() callback runs after the Promise settles, regardless of whether the result was successful or failed.

Using finally() for Cleanup

function loadData() {
  console.log("Loading started");

  return Promise.reject(new Error("Network error"))
    .catch(function (error) {
      console.log(error.message);
    })
    .finally(function () {
      console.log("Loading indicator hidden");
    });
}

loadData();

Output:

Loading started
Network error
Loading indicator hidden

In a webpage, the final callback could hide a loading spinner or re-enable a button.

A normal .finally() callback preserves the previous fulfillment value or rejection reason. However, if the callback itself throws an error or returns a rejected Promise, the Promise returned by .finally() will reject with that new failure.

Promise Chaining in JavaScript

Promise chaining means connecting multiple .then(), .catch(), and .finally() calls so that one operation can follow another.

It is useful when several asynchronous steps depend on previous results.

Each .then() returns a new Promise, allowing the next step to receive the previous step’s result.

Example of Promise Chaining

Promise.resolve(5)
  .then(function (number) {
    return number * 2;
  })
  .then(function (number) {
    return number + 10;
  })
  .then(function (result) {
    console.log(result);
  });

Output:

20

Here is how the chain works:

  • The first Promise provides the value 5.
  • The first .then() multiplies the value by two, returning 10.
  • The second .then() adds ten, returning 20.
  • The final .then() prints 20.

Chaining Asynchronous Operations

Consider a simplified process in which an application retrieves a user and then retrieves that user’s orders.

function getUser() {
  return Promise.resolve({
    id: 1,
    name: "Rahul"
  });
}

function getOrders(user) {
  return Promise.resolve([
    "Order 1 for " + user.name,
    "Order 2 for " + user.name
  ]);
}

getUser()
  .then(function (user) {
    return getOrders(user);
  })
  .then(function (orders) {
    console.log(orders);
  })
  .catch(function (error) {
    console.log("Error:", error.message);
  });

Output:

[
  "Order 1 for Rahul",
  "Order 2 for Rahul"
]

In this example, getUser() returns a Promise containing a user object. The first .then() passes that user to getOrders(), and the next .then() receives the resulting array.

The example uses immediately fulfilled Promises to demonstrate the structure. In a real application, these functions might perform network requests and return Promises that settle later.

Avoiding Common Promise Chaining Mistakes

Forgetting to return a Promise

getUser().then(function (user) {
  getOrders(user);
});

In this example, the callback does not return the Promise from getOrders(). The outer chain therefore does not wait for that operation to settle.

The corrected version is:

getUser().then(function (user) {
  return getOrders(user);
});

Returning the Promise allows the chain to wait for its outcome and pass its result to the next handler.

Forgetting error handling

A rejected Promise can cause the fulfillment handlers to be skipped. Use .catch() when you need to handle failures in the chain.

Nesting unnecessarily

Instead of placing a new .then() chain inside every callback, return the next Promise and continue the same chain. This generally makes asynchronous code easier to read and maintain.

Common Promise Methods

JavaScript provides additional static Promise methods for working with multiple Promises.

MethodPurpose
Promise.resolve(value)Creates a Promise fulfilled with a value or adopting a supplied Promise or thenable.
Promise.reject(reason)Creates a rejected Promise with the specified reason.
Promise.all(iterable)Fulfills when all inputs fulfill, or rejects when one input rejects.
Promise.allSettled(iterable)Waits for all inputs to settle and returns their outcomes.
Promise.race(iterable)Settles with the outcome of the first input to settle.
Promise.any(iterable)Fulfills with the first fulfillment, or rejects if every input rejects.

These methods are especially useful when working with multiple asynchronous operations. They will be explored in more detail in the upcoming Promise methods tutorial.

Best Practices for Using Promises

Follow these practices when working with Promises:

  • Return Promises from functions that perform asynchronous operations.
  • Return values from .then() handlers when the next step needs them.
  • Use .catch() to handle errors in a Promise chain.
  • Use .finally() for cleanup that should happen after settlement.
  • Prefer descriptive error messages that help identify the problem.
  • Avoid unnecessary nesting of Promise chains.
  • Do not assume that creating a Promise automatically makes a synchronous operation non-blocking.
  • Remember that Promise handlers run asynchronously, even when the Promise is already fulfilled.
  • Use async and await when they make asynchronous workflows easier to understand.

Frequently Asked Questions

What is a Promise in JavaScript?

A Promise is an object that represents the eventual fulfillment or rejection of an operation. It provides methods for handling results and errors.

What are the three states of a Promise?

The three states are pending, fulfilled, and rejected. Fulfilled and rejected Promises are both considered settled.

What is the difference between resolve() and reject()?

resolve() fulfills a Promise with a value or makes it adopt another Promise’s outcome. reject() rejects a Promise with a specified reason.

What is the purpose of then()?

The .then() method registers fulfillment and optional rejection handlers. It also returns a new Promise, allowing operations to be chained.

What is the purpose of catch()?

The .catch() method registers a rejection handler. It can handle failures from the original Promise and errors thrown by earlier handlers in the chain.

What is the purpose of finally()?

The .finally() method runs a callback after a Promise settles, regardless of whether it fulfills or rejects. It is commonly used for cleanup.

What is Promise chaining?

Promise chaining connects multiple Promise handlers so that each step can use the previous step’s result. It also allows errors to propagate through the chain to a rejection handler.

Are Promises always asynchronous?

The executor function passed to new Promise() runs immediately. However, .then(), .catch(), and .finally() handlers run asynchronously as Promise reactions.

Conclusion

JavaScript Promises provide a structured way to handle asynchronous operations and their outcomes. By understanding Promise states, creating Promises, using resolve() and reject(), and handling results with .then(), .catch(), and .finally(), you can manage asynchronous tasks more effectively.

Promise chaining allows you to connect multiple operations without deeply nested callbacks. These concepts form the foundation for working with APIs, network requests, and modern JavaScript features such as async and await.