JavaScript Promise

JavaScript_Promise_Feature.jpg

By the end of this guide, you’ll not only understand what JavaScript Promises are, but you’ll be able to wield them with confidence, avoid the most common mistakes, and even master advanced patterns that make your asynchronous code a joy to work with.

Table of Contents:

What are Promises in JavaScript?

Imagine that you’re at a coffee shop. You order a cappuccino and pay at the counter. Rather than stand at the counter, you take a seat, open your laptop, and get to work. After a few minutes, someone calls your name and you get your coffee, and hopefully got some work done during the wait. 


This is exactly how JavaScript Promises work. JavaScript runs one task at a time, but Promises let you start an operation (like fetching data) without stopping the rest of your code. When the result is ready, the Promise handles it.

The best part? Promises replaced the messy, headache-inducing “callback hell” that used to plague JavaScript developers. They’re cleaner, easier to read, and pair perfectly with modern async tools like async/await.

How JavaScript Promises Work Under the Hood (Microtask Queue, Event Loop)

When you use then() in JavaScript, it might look like the code runs immediately after the Promise resolves, but actually, JavaScript adds it to a special queue called the microtask queue. The event loop then runs these tasks in order, keeping everything smooth and organized.

Here’s the big picture:

  • Call Stack: This is where JavaScript runs synchronous code, line by line.
  • Callback (Macrotask) Queue: Things like setTimeout and DOM events wait here until the stack is clear.
  • Microtask Queue: This is where Promise callbacks (then, catch, finally) go. And here’s the twist:
    • The event loop processes all microtasks before moving on to the next macrotask
    • The event loop processes all microtasks before moving on to the next macrotask
JavaScript Promises Work

Promise in JavaScript Example:

Javascript

Output:

Output 1

Even though both the Promise and the timeout are scheduled after the synchronous code, the Promise wins because its callback is in the microtask queue, which runs first.

Why this matters:

  • If you understand this order, you can write more predictable async code.
  • You’ll avoid subtle bugs where you think something should happen later… but it doesn’t.
  • You’ll also be able to explain those “weird” timing issues that stump a lot of developers.

Now that you have a fair understanding of how JavaScript promises work, in the next section we will discuss the JavaScript Promise Constructor, which is how you can make a JavaScript Promise yourself without dealing with any pre-determined logic.

JavaScript Promise Constructor

The JavaScript Promise constructor is the official way to create new promises in JavaScript. Its syntax looks like this:

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

  // executor function

});

The JavaScript Promise constructor takes a single argument. An executor function, which itself has two parameters:

  • resolve(value): used to mark the promise as fulfilled and return a value.
  • reject(error): used to mark the promise as rejected and return an error.

The moment you create a new JavaScript Promise, the executor runs immediately. This is important to understand because it means your asynchronous task starts right away, not lazily when .then() is attached.

Simple JavaScript Promise Example

Javascript

Output:

Output 2

In this JavaScript Promise example, since success is true, the JavaScript Promise resolves and the .then() block runs. If success were false, the .catch() block would execute instead. Let’s move on to a more real-world scenario of using Promises to manage asynchronous operations.

JavaScript Promise Constructor Example: Wrapping setTimeout in a JavaScript Promise

One of the most common uses of the JavaScript Promise constructor is turning callback-based APIs into promises.

Javascript

Output(After 2 Seconds):

Output 3

Key Points to Remember about the JavaScript Promise Constructor

  • The executor runs right away, not when .then() is called.
  • Only the first call to resolve or reject matters; extra calls are ignored.

The JavaScript Promise constructor is powerful but often abstracted away by APIs (like fetch) that already return promises. You’ll use it mostly when converting older callback-based code into JavaScript Promises.

Promise Methods in JavaScript

So far, we have discussed what a Promise is in JavaScript and how to create one, but unless you consume it, it’s like brewing coffee and never drinking it.

In JavaScript, you “drink” your JavaScript Promise with then(), “handle the bitter taste” with catch(), and “clean the cup” with finally().

1. then()

The code inside the then() keyword in JavaScript is used to store the code that will run once a Promise is resolved. 

Javascript

Output:

Output 4

You can also chain multiple .then() calls,  each of which receives the result from the previous

Javascript

Output:

Output 5

2. .catch()

Promises can fail, and when they do, .catch() is your safety net.

Javascript

Output:

Output 6

3. finally()

Whether your JavaScript Promise resolves or rejects, .finally() is guaranteed to run. Think of it as turning off the coffee machine, no matter how your morning went.

Javascript

Output:

Output 7

4. Chaining with All Three

You can combine then, catch, and finally, for robust async handling:

Javascript

Output:

Output 8
  • Use catch() at the end of your chain to handle any errors along the way.
  • Keep finally() for cleanup actions, like hiding loaders, closing connections, or clearing timers.

If you master then, catch, and finally, you’ve already conquered half of async JavaScript. The next step? Learning how to chain and compose multiple JavaScript Promises without getting lost in callback hell.

JavaScript Promise Combinators (Static Methods)

In real-world applications, we rarely have promises that work in silos. In fact, most of the time, you’ll need to fetch multiple datasets, run parallel API calls, and process different files, all at the same time! But don’t worry, JavaScript provides Promise combinators (static methods), which act like a traffic controller of sorts. 

1. Promise.all()

Runs all JavaScript Promises in parallel and waits until every one of them resolves.

If any of them rejects, the whole thing fails.

Javascript

Output:

Output 9

2. Promise.allSettled() 

Waits for all Promises to finish, regardless of whether they succeed or fail.

Useful when you care about every result, even the failed ones.

Javascript

Output:

Output 10

3. Promise.race()

Returns the result of the first Promise that settles (whether it resolves or rejects).

Javascript

Output:

Output 11

4. Promise.any()

Waits for the first Promise to resolve, ignoring rejections until all fail.

If all fail, it throws an AggregateError.

Javascript

Output:

Output 12

When to Use Which?

  • Promise.all(): All must succeed (e.g., load multiple resources together).
  • Promise.allSettled(): Need the status of each task (e.g., batch processing).
  • Promise.race(): First one to finish matters (e.g., fastest API mirror).
  • Promise.any(): You only care about the first successful one.

Static Properties of JavaScript Promise

Unlike static methods, static properties of JavaScript Promise are limited to a  few. You will not see it used a lot in day-to-day code, and you will see why. 

1. Promise.length

This property tells you how many arguments the Promise constructor expects.

Since the constructor takes one executor function (new Promise(executor)), its length property is always 1.

Javascript

Output:

Output 13

As you can see, this isn’t something you’ll use while writing applications, but it helps confirm that the constructor always takes a single executor function.

2. Promise.prototype

Every Promise instance inherits from Promise.prototype.

This property holds the methods (then, catch, and finally) that you use on individual promises.

Javascript

Output:

Output 14

This shows that when you call .then() or .catch() on a promise, you’re actually using methods defined on Promise.prototype.

Thenables in JavaScript

When people first learn about JavaScript Promises, they usually assume only Promise objects can be chained with .then(). But here’s a little secret: any object with a .then() method is treated like a JavaScript Promise. These objects are called thenables.

Think of thenables as “promise lookalikes”. They may not be actual instances of Promise, but JavaScript doesn’t really care, as long as something has a .then() method, the engine will try to use it in a promise-like way.

Why Thenables Matter

  • They allow interoperability between libraries. For example, older async libraries that predate native Promises can still work with newer promise-based code.
  • They make it possible to create custom async abstractions without directly using the Promise constructor.
  • They explain some “weird” behaviors developers face when mixing third-party libraries with Promises.

Example

Javascript

Output:

Output 15

In the example above, if you pay close attention, thenableObj is not a real JavaScript Promise. It’s just a plain old object with a .then() method.

When we pass it to a Promise.resolve(), JavaScript checks:

  • Does this object have a .then()?
  • If yes, it treats it like a Promise and calls that method.
  • The resolve callback inside .then() gets executed after 1 second, just like a real Promise.

Thenables are a perfect example showcasing that JavaScript Promises are about behaviour and not class inheritance. Any object that acts like a Promise can be used where a Promise is expected. This is why Promise.resolve() is so powerful, it can convert both real Promises and thenables into proper promises that behave consistently.

Async/Await with JavaScript Promises

If JavaScript Promises were a huge leap forward from callback hell, then async/await is the comfy armchair we’ve all been waiting for. It lets you write asynchronous code that looks synchronous, without sacrificing performance.

Instead of chaining .then() calls like you’re building a Lego tower, you can use await to pause until a Promise settles, and then move on.

The result is cleaner, more readable code, which is always good to have.

Basic Example of Async/Await

Javascript

Output:

Output 16
  • The async keyword makes the function return a Promise automatically.
  • The await keyword pauses execution until the Promise resolves, then assigns the resolved value to result.
  • This eliminates the need for chaining .then() and improves readability.

Handling Errors with Async/Await

Just like synchronous code uses try...catch, you can wrap await calls inside it to gracefully handle rejections.

Javascript

Output:

Output 17

Here, the Promise rejects after 1 second. Instead of using .catch(), we use try...catch to handle errors, keeping everything clean and synchronous-looking.

Async/Await vs. Then Chaining

Let’s compare the two approaches quickly:

Using .then() chaining:

 fetchData()
  .then((res) => console.log(res))
  .catch((err) => console.error(err));

Using async/await:

async function run() {
  try {
    const res = await fetchData();
    console.log(res);
  } catch (err) {
    console.error(err);
  }
}
run();

Both methods achieve the same result, but async/await often feels more intuitive, especially when dealing with multiple asynchronous operations.

JavaScript Promise Examples

You should by now have a solid understanding of how Promises in JavaScript work, how to create them, and even use async/await to make your code more efficient and readable. The best way to master any concept in JavaScript is to apply it to real-world examples. Below are different examples, starting from the basics and gradually moving into more practical scenarios.

1. Basic JavaScript Promise Example

Javascript

Output:

Output 18

Here, we create a JavaScript Promise that either resolves with a success message or rejects with an error. Since success is true, the .then() block runs and prints the result. If you switch success to false, the .catch() block will handle the error.

2. JavaScript Promises Chaining Example

Javascript

Output:

Output 19

Chaining lets you run sequential operations where the output of one .then() becomes the input for the next. This is much cleaner than deeply nested callbacks, making code easier to read and maintain.

3. JavaScript Promise Concurrency Example

Javascript

Output:

Output 20

Here we use Promise.all() to run two promises at the same time. The result only arrives when both promises finish, which is great for parallel API calls or loading multiple resources together.

Example of JavaScript Promise: Fetching Data with fetch()

<!DOCTYPE html>

<html>
<head>
  <title>Promise Example</title>
</head>
<body>
  <script>
    fetch("https://jsonplaceholder.typicode.com/posts/1")
      .then(response => response.json())
      .then(data => console.log("Post Title:", data.title))
      .catch(error => console.error("Error:", error));
  </script>
</body>
</html>

Possible Output:

Post Title: sunt aut facere repellat provident occaecati excepturi optio reprehenderit

This is where JavaScript Promises shine in real-world development. The fetch() API returns a Promise that resolves once the data is received. We can chain .then() calls to parse the JSON and log the result. If something goes wrong (like network issues), .catch() ensures the error is handled gracefully.

JavaScript Promise Specification

JavaScript Promises are much more than just a coding pattern. They are a formal part of the ECMAScript specification (ES6 and later), meaning that JavaScript Promises behave the same way across all modern browsers and JavaScript environments, ensuring developers can write consistent, reliable asynchronous code.

According to the specification, a Promise is defined as an object that:

  • Represents the eventual completion (fulfilled) or failure (rejected) of an asynchronous operation.
  • Provides a thenable interface (then, catch, finally) to react to those states.
  • Guarantees that callbacks registered via .then() or .catch() are always executed asynchronously, after the current call stack clears.

This standardization is what allows features like async/await and Promise combinators (Promise.all, Promise.race, etc.) to work the same way everywhere, from Chrome to Node.js.

JavaScript Promise Browser Compatibility

JavaScript Promises are now supported in all modern browsers, but older versions (especially Internet Explorer) lack native support. In those cases, developers can use a polyfill (like core-js) to ensure compatibility.

BrowserSupported VersionNotes
Google Chrome32+Full support
Mozilla Firefox29+Full support
Safari8+Full support
Microsoft Edge12+Full support
Opera19+Full support
Internet ExplorerNot SupportedUse a polyfill

JavaScript Promises Cheat Sheet

ConceptSyntax / MethodDescription
Creating a Promisenew Promise((resolve, reject) => { ... })/code>Defines an async task with success (resolve) or failure (reject).
then()promise.then(onFulfilled)Handles resolved values.
catch()promise.catch(onRejected)Handles errors.
finally()promise.finally(callback)Runs regardless of success or failure.
Promise.resolve()Promise.resolve(value)Creates a resolved promise.
Promise.reject()Promise.reject(error)Creates a rejected promise.
Promise.all()Promise.all([p1, p2])Resolves when all promises resolve, or rejects if any fail.
Promise.allSettled()Promise.allSettled([p1, p2])Resolves after all promises finish, regardless of outcome.
Promise.race()Promise.race([p1, p2])Resolves/rejects with the first settled promise.
Promise.any()Promise.any([p1, p2])Resolves with the first fulfilled promise; rejects if all fail.
Async/Awaitasync function() { await promise }Cleaner syntax for working with promises.

Conclusion

JavaScript Promises simplify asynchronous programming, replacing messy callback patterns with a cleaner, more reliable approach. From handling API calls to running tasks in parallel, JavaScript Promises, along with async/await, are essential for writing modern JavaScript.
If you want to go beyond the basics and build complete applications, our Full Stack Development Course will help you master JavaScript, backend development, databases, and deployment, everything you need to become job-ready.

JavaScript Promises - FAQ

Q1. What is Promise chaining?

Promise chaining in JavaScript means doing several tasks one after the other, like a chain. Once one task finishes, the next one starts. Promise chaining helps in writing clean and structured code.

Q2. What is Promise.all()?

If you have multiple tasks happening at the same time, Promise.all() allows you to wait for all of them to finish before moving on to the next task.

Q3. What is Promise.race()?

Promise.race() is when you only care about the first task that finishes. The moment one task is done, you take action, even if the others are still running.

Q4. What is async/await?

async/await is a simpler way to work with promises. It lets you write asynchronous code (code that takes time) in a way that looks like normal synchronous code (code that runs step-by-step). It helps make the code easier to understand.
– async means the function deals with promises.
– await means “wait for this promise to finish before moving on.”

Q5. What happens if you don’t handle Promise errors?

If you don’t handle errors in promises, they might go unnoticed, and your program might break later. It’s important to always handle errors when using promises so that things don’t go wrong silently.

About the Author

Technical Research Analyst - Full Stack Development

Kislay is a Technical Research Analyst and Full Stack Developer with expertise in crafting Mobile applications from inception to deployment. Proficient in Android development, IOS development, HTML, CSS, JavaScript, React, Angular, MySQL, and MongoDB, he’s committed to enhancing user experiences through intuitive websites and advanced mobile applications.

Full Stack Developer Course Banner