JavaScript Promises lets you handle tasks that take time, like getting data, simply and clearly. They make the code easier to read and keep things from getting messy. Instead of using many callbacks, Promises allow you to chain actions in a straight line. So, in this Javascript promise tutorial we will see how you can use methods like .then() to handle success, .catch() for errors, and .finally() to do cleanup. Promises also let you handle multiple tasks at once with tools like Promise.all() and Promise.race(). Learning how to use Promises helps you write better and cleaner JavaScript code.

What are Promises in JavaScript?

A JavaScript Promises are like a promise you make in real life: it is something that will happen in the future. It helps you handle tasks that take time, like loading data or waiting for a file, simply. A promise can be in three stages: pending (the task is still happening), fulfilled (the task finished successfully), or rejected (the task failed). You can use .then() to handle a successful task and .catch() for an error. Promises also allow you to link multiple tasks together, making it easy to run them one after the other without stopping the rest of your code.

JavaScript Promise States

JavaScript Promises either get fulfilled (success) or fall flat (failure). It has three stages:

  1. Pending: The promise is still being worked on, and the result isn’t ready yet.
  2. Fulfilled (Resolved): The task was completed successfully, and you get the result.
  3. Rejected: The task failed, and there’s an error or problem.

Here’s an example:

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

    let success = true; // Change to false to see the failure

 

    if (success) {

        resolve("The operation was successful!"); // Success

    } else {

        reject("The operation failed!"); // Failure

    }

});

 

myPromise

    .then(result => {

        console.log(result); // Logs success if the promise is kept

    })

    .catch(error => {

        console.log(error); // Logs failure if the promise is broken

    });


  • If success is true, the JavaScript Promises are fulfilled and show the success message.
  • If success is false, the promise is rejected and shows the failure message.

Example of a Basic Promise

Let's explore and create a promise JavaScript example to understand how promises work in practice:

const myPromise = new Promise((resolve, reject) => {

    const success = true;

 

    if(success) {

        resolve('Operation was successful!');

    } else {

        reject('Operation failed.');

    }

});

 

myPromise.then(result => {

    console.log(result); // Logs: Operation was successful!

}).catch(error => {

    console.error(error); // Logs: Operation failed.

});


In this example, the JavaScript Promises are created using the new Promise constructor, which takes a function with two parameters: resolve and reject. Depending on the result of the asynchronous operation, the promise is either resolved or rejected. Using the .then() method, we handle successful resolution, while.catch() handles any rejection.

How Does a JavaScript Promise Work?

A JavaScript Promise is an object that represents the eventual completion (or failure) of an asynchronous operation. It works by allowing you to attach .then() and .catch() handlers to handle the success or failure of an operation without blocking the main thread of execution. Here's a breakdown of how a promise works:

1. Creating a Promise

When JavaScript Promises are created, it takes an executor function with two parameters:

  • resolve: A function that you call when the operation succeeds.
  • reject: A function that you call when the operation fails.

const myPromise = new Promise((resolve, reject) => {

  // Simulate a task (e.g., fetching data)

  let success = true; // This could depend on some condition

 

  if (success) {

    resolve("Task succeeded");

  } else {

    reject("Task failed");

  }

});


2. Using .then() and .catch()

  • .then() is used to specify what to do when the promise is fulfilled.
  • .catch() is used to handle any errors if the promise is rejected.

myPromise

  .then(result => {

    console.log(result);  // Logs: "Task succeeded"

  })

  .catch(error => {

    console.log(error);  // Logs: "Task failed"

  });


3. Chaining Promises

JavaScript Promises can be chained using .then(). The value returned from one .then() can be passed to the next .then() in the chain.

myPromise

  .then(result => {

    console.log(result);  // Logs: "Task succeeded"

    return "Next step";

  })

  .then(nextResult => {

    console.log(nextResult);  // Logs: "Next step"

  })

  .catch(error => {

    console.log(error);

  });


4. Promise Resolution

  • If the promise is resolved (fulfilled), the resolve function is called, and the .then() block executes.
  • If the promise is rejected, the reject function is called, and the .catch() block executes.
Example with Asynchronous Operations

JavaScript Promises are typically used to handle asynchronous operations like network requests or file I/O. For example, fetching data:

function fetchData() {

  return new Promise((resolve, reject) => {

    setTimeout(() => {

      const data = { user: "John", age: 30 };

      resolve(data);  // Data fetched successfully

    }, 2000);

  });

}

 

fetchData()

  .then(data => {

    console.log(data);  // Logs: { user: "John", age: 30 }

  })

  .catch(error => {

    console.log(error);

  });


Why Use JavaScript Promises?

They are essential for handling asynchronous operations, and they offer several benefits over older methods, such as callbacks, especially in managing complex tasks and error handling. Here are some key reasons why you should use promises:

1. Avoid Callback Hell (Pyramid of Doom)

  • Problem with Callbacks: When multiple asynchronous operations depend on each other, you can end up with deeply nested callbacks, making the code harder to read and maintain.
  • Solution with Promises: Promises allow for cleaner, more readable code by chaining .then() and .catch() methods. Each asynchronous operation can be neatly separated and handled sequentially without deep nesting.

Example: Callback Hell

asyncFunction1(function(result1) {

  asyncFunction2(function(result2) {

    asyncFunction3(function(result3) {

      // Further nested functions

    });

  });

});

 

With Promises:

asyncFunction1()

  .then(result1 => asyncFunction2())

  .then(result2 => asyncFunction3())

  .catch(error => console.error(error));


2. Better Error Handling

  • Problem with Callbacks: Errors in callback-based approaches need to be handled explicitly in each callback, leading to duplicated error handling code.
  • Solution with Promises: JavaScript Promises simplify error handling by providing a single .catch() at the end of a promise chain, ensuring all errors from any asynchronous operation in that chain are handled centrally.

Example with Callbacks:

asyncFunction1(function(error, result1) {

  if (error) { return handleError(error); }

  asyncFunction2(function(error, result2) {

    if (error) { return handleError(error); }

    // More code

  });

});

 

With Promises:

asyncFunction1()

  .then(result1 => asyncFunction2())

  .catch(error => handleError(error));  // Centralized error handling


3. Easier to Chain Asynchronous Operations

  • Problem with Callbacks: When dealing with multiple dependent asynchronous operations, callbacks can become difficult to manage and hard to maintain.
  • Solution with Promises: JavaScript Promises allow you to chain multiple asynchronous operations, ensuring that the results of one operation can be used in subsequent operations in a clean, readable way.

Example:

doTask1()

  .then(result1 => doTask2(result1))

  .then(result2 => doTask3(result2))

  .catch(error => console.error("Error:", error));


4. Cleaner Asynchronous Code

Promises make asynchronous code more structured and easier to understand. You can write asynchronous operations that look synchronous, improving readability.

Example:

doSomethingAsync()

  .then(response => processResponse(response))

  .then(() => nextTask())

  .catch(error => handleError(error));


5. Promise Chaining and Composition

JavaScript Promises allow for easy chaining and composing multiple promises, which is particularly useful for managing operations like parallel asynchronous requests or sequential execution.

Example:

// Chaining promises for sequential execution

doFirstTask()

  .then(result => doSecondTask(result))

  .then(result => doThirdTask(result))

  .catch(error => console.log("Error:", error));

 

// Running multiple promises in parallel

Promise.all([doFirstTask(), doSecondTask()])

  .then(([result1, result2]) => {

    console.log("Both tasks completed:", result1, result2);

  })

  .catch(error => console.log("Error in parallel tasks:", error));


6. Better Support for Asynchronous Control Flow

Promises integrate well with modern asynchronous control flow techniques, like async/await, which can make the code look and behave like synchronous code, further improving readability and reducing complexity.

Example with async/await:

 

async function executeTasks() {

  try {

    let result1 = await doTask1();

    let result2 = await doTask2(result1);

    console.log(result2);

  } catch (error) {

    console.error("Error:", error);

  }

}

executeTasks();


7. Promises Are Composable

JavaScript Promises can be composed in various ways:

  • Promise.all() can be used to execute multiple promises in parallel and wait for all of them to complete.
  • Promise.race() can be used to wait for the first promise to complete, no matter whether it resolves or rejects.
  • Promise.allSettled() waits for all promises to settle (either resolved or rejected), and Promise.any() returns the first promise that resolves, ignoring rejections.

Example:

Promise.all([fetchData1(), fetchData2()])

  .then(([data1, data2]) => {

    console.log(data1, data2);

  })

  .catch(error => console.log("Error:", error));


8. Standardized Asynchronous Interface

Promises are standardized in JavaScript (since ES6), which makes asynchronous code more predictable and easier to work with across different libraries and frameworks.

9. Improves Debugging

Debugging asynchronous code can be difficult. JavaScript Promises provide stack traces in most modern JavaScript environments, which makes it easier to track down where an error occurred, even in asynchronous code.

How to Create a JavaScript Promise?

Creating a JavaScript new promise is straightforward. The following code shows the basic structure:

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

    // asynchronous code here

    if (/* condition */) {

        resolve('Success!');

    } else {

        reject('Error!');

    }

});


The function passed to the new Promise() constructor accepts two parameters:

  • resolve: A function used to mark the promise as successful.
  • reject: A function used to mark the promise as failed.

Once the promise is created, you can use .then() and .catch() to handle the results.

Chaining Promises in JavaScript

A JS promise example of chaining multiple promises looks like this:

fetchData()

    .then(data => processData(data))

    .then(processedData => saveData(processedData))

    .catch(error => handleError(error));

In this example:

  • fetchData() returns a promise, and when it resolves, the next promise (processData()) is executed.
  • If any promise is rejected, the .catch() block will handle the error.

Chaining is essential because it allows asynchronous operations to be executed sequentially, ensuring that each step occurs only after the previous one has been completed.

JavaScript Promises vs Callbacks

Even though callbacks are still used in JavaScript, Promises have some big advantages:

  • Easier to Read: Promises make async code easier to understand by writing it in a simple, step-by-step order, instead of nesting callbacks.
  • Better Error Handling: With promises, you can handle errors in one place, without repeating error code everywhere.
  • No More Callback Hell: Promises help you avoid having to nest many callbacks inside each other. Which can make your code messy and hard to read.

Common Methods Used with JavaScript Promises

They come with several useful methods to handle asynchronous operations. Here are the most common ones:

1. then()

  • Used to specify what to do when the promise is fulfilled (resolved).
  • It takes two arguments: a callback function for handling the result (fulfilled state) and a callback for handling errors (rejected state).

Example:

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

    resolve("Success!");

});

 

promise.then(result => {

    console.log(result); // Logs "Success!"

}).catch(error => {

    console.log(error); // Will not be called in this case

});


2. catch()

  • Used to handle errors when any of the JavaScript Promises are rejected.
  • It only handles the error and is equivalent to the second argument of then().

Example:

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

    reject("Something went wrong!");

});

 

promise.catch(error => {

    console.log(error); // Logs "Something went wrong!"

});


3. finally()

  • Executes a callback function after the promise is settled (either fulfilled or rejected).
  • Useful for cleanup or actions that should occur no matter the outcome.

Example:

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

    resolve("Operation complete");

});

 

promise.finally(() => {

    console.log("Cleanup or final action here"); // This runs regardless of fulfillment or rejection

});


4. Promise.all()

Accepts an array of JavaScript Promises and returns a single promise that resolves when all the input promises are fulfilled or rejected as soon as one of the promises is rejected.

Example:

let promise1 = new Promise((resolve) => resolve("First"));

let promise2 = new Promise((resolve) => resolve("Second"));

 

Promise.all([promise1, promise2]).then(results => {

    console.log(results); // Logs ["First", "Second"]

});


5. Promise.race()

Returns a promise that resolves or rejects as soon as one of the input promises settles (either resolves or rejects).

Example:

let promise1 = new Promise((resolve) => setTimeout(resolve, 100, "First"));

let promise2 = new Promise((resolve) => setTimeout(resolve, 200, "Second"));

 

Promise.race([promise1, promise2]).then(result => {

    console.log(result); // Logs "First" because it resolves first

});


6. Promise.allSettled()

Returns a promise that resolves after all the given JavaScript Promises have settled (either fulfilled or rejected), providing an array of objects with the status of each promise.

Example:

let promise1 = Promise.resolve("Success");

let promise2 = Promise.reject("Error");

 

Promise.allSettled([promise1, promise2]).then(results => {

    console.log(results);

    // Logs: [

    //   { status: 'fulfilled', value: 'Success' },

    //   { status: 'rejected', reason: 'Error' }

    // ]

});


Conclusion

JavaScript Promises are essential for efficiently handling asynchronous operations. They enable smooth execution of API requests, database queries, and event handling. Using the .then(), .catch(), and .finally() methods, they prevent callback hell and simplify error management.

While JavaScript uses Promises for asynchronous programming, Python achieves similar functionality with async/await and the asyncio module. Learning Python programming can help you understand asynchronous execution beyond JavaScript, making it easier to work with real-world data processing and automation tasks.

Frequently Asked Questions (FAQs)
Q. What are the main rules of promise?

Ans. A Promise has three states: pending (still working), fulfilled (finished successfully), or rejected (failed). Once it's settled, it can't change. Promises run tasks without stopping the rest of the code.

Q. What is the benefit of promise in JavaScript?

Ans. JavaScript Promises make your code easier to read, avoid messy callbacks, let you chain tasks together, handle errors in one place with .catch(), and run multiple tasks at once using methods like Promise.all() and Promise.race().