JavaScript Promise: A Beginner to Advanced Guide

JavaScript
5 minutes read

Introduction

Promise in JavaScript are powerful tools for handling asynchronous operations, but they can sometimes seem challenging, especially if you’re new to JavaScript or asynchronous programming. Don’t worry! Whether you’re a beginner or an experienced developer, this comprehensive guide will help you master JavaScript Promises in no time.

Understanding JavaScript Promise

First, let’s unpack what a Promise is. In JavaScript, a Promise is an object that represents the final completion (or failure) of an asynchronous operation. It’s like a placeholder for a value not known when the Promise is created.

There are three states a Promise can be in:

  • Pending: This is the initial state— the operation is ongoing.
  • Fulfilled: The operation was completed successfully, and the Promise has a resulting value.
  • Rejected: The operation failed, and the Promise has a reason for the failure.

Think of ordering a product online. When you place the order, it’s like creating a Promise. The order can be pending (being processed), fulfilled (delivered), or rejected (maybe the item was out of stock).

let promise = new Promise((resolve, reject) => {
    let orderFulfilled = true; // This would typically be some asynchronous operation
    
    if (orderFulfilled) {
        resolve('Order delivered!');
    } else {
        reject('Order could not be fulfilled.');
    }
});

promise.then((message) => {
    console.log(message); // Output: Order delivered!
}).catch((message) => {
    console.log(message);
});

The above code demonstrates the creation and usage of a Promise, an object that represents the eventual completion or failure of an asynchronous operation. 

In the above code snippet, we created a new Promise object with a function that checks the orderFulfilled boolean variable. 

If orderFulfilled is true, the Promise is resolved using the resolve function, passing the string ‘Order delivered!‘; if false, it’s rejected using the reject function with the reason ‘Order could not be fulfilled.‘  Following the Promise creation, .then() and .catch() methods are chained to handle the fulfilled and rejected cases respectively. 

In our example, if the Promise is fulfilled, the message ‘Order delivered!‘ is logged to the console; if rejected (which it won’t be in this case as orderFulfilled is true), ‘Order could not be fulfilled.’ would be logged.

Working with Promises

Promises are used with the .then(), .catch(), and .finally() methods, which allow us to attach callbacks to handle the fulfilled value or the reason for rejection.

The .then() method is used when a Promise is fulfilled. It takes two arguments: a callback function for the success case and another for the failure case. Importantly, .then() also returns a new Promise, allowing for method chaining.

The .catch() method is used when a Promise is rejected. We can add .catch() at the end of a Promise chain to handle any errors that might occur during the execution of the previous Promises.

The .finally() method is used when you want to perform some task regardless of the Promise’s fulfillment or rejection.

let promise = new Promise((resolve, reject) => {
    let orderFulfilled = true;
    
    if (orderFulfilled) {
        resolve('Order delivered!');
    } else {
        reject('Order could not be fulfilled.');
    }
});

promise
    .then((message) => {
        console.log(message); // Output: Order delivered!
        return 'Thank you for your order.';
    })
    .then((message) => {
        console.log(message); // Output: Thank you for your order.
    })
    .catch((message) => {
        console.log(message);
    }
    .finally(()) => {
        console.log(“This message is printed regardless of the promise succeeded or failed”)
     });

Promise.all()

Promise.all() is a very useful method when dealing with multiple promises. It accepts an iterable (like an array) of promises and returns a new promise that is only fulfilled when all of the passed promises have been fulfilled or rejected as soon as one of them rejects.

Here is the structure of Promise.all():

Promise.all([promise1, promise2, promise3, ...])
    .then((values) => {
        // All promises are fulfilled here
    })
    .catch((reason) => {
        // If any promise was rejected, catch will be executed
    });

In the .then method of Promise.all(), the values parameter is an array that contains the fulfillment values of all the promises, in the same order as the promises passed to Promise.all().

In the .catch method, the reason parameter is the rejection reason of the first promise that was rejected.

Let’s take an example:

let promise1 = Promise.resolve('First');
let promise2 = Promise.resolve('Second');
let promise3 = Promise.resolve('Third');

Promise.all([promise1, promise2, promise3])
    .then((values) => {
        console.log(values); // Output: ['First', 'Second', 'Third']
    });

In this example, Promise.all() is passed an array of three promises, each resolving with a string. Since all promises resolve, the .then method is called, and the output is an array of the values with which each promise was resolved.

One key thing to remember about Promise.all() is that if any of the promises its tracking rejects, Promise.all() will immediately reject with the reason of the promise that rejected, discarding all the other promises, even if they resolve successfully.

Here’s an example of that behavior:

et promise1 = Promise.resolve('First');
let promise2 = Promise.reject('Second promise failed');
let promise3 = Promise.resolve('Third');

Promise.all([promise1, promise2, promise3])
    .then((values) => {
        console.log(values);
    })
    .catch((reason) => {
        console.log(reason); // Output: 'Second promise failed'
    });

In this example, even though promise1 and promise3 resolve successfully, Promise.all() rejects because promise2 rejects. The catch method is called, and the output is the reason why promise2 was rejected.

This behavior makes Promise.all() a great tool when you need to run multiple promises in parallel and only proceed when all of them are successful.

Promise Chaining

Promise chaining is like a series of steps to be done one after the other, where each step waits for the previous one to finish before it starts.

  • Step 1 – Starting the Chain: Think of it as you’re starting a task. That’s your initial Promise.
let task1 = doSomething();
  • Step 2 – Next Task: Once the first task is done, you want to do something else, right? That’s where .then() comes in. It waits for the previous task to complete and then starts the next one.
let task2 = task1.then(doSomethingElse);

Here, doSomethingElse only starts after task1 is done. The result of doSomethingElse is now task2.

  • Step 3 – Keep Going: You can continue this process, adding as many tasks as you want. Each task will wait for the previous one to finish.
task2.then(doThirdThing);

Here, doThirdThing waits for task2 to finish.

  • Step 4 – Oops, an Error: If something goes wrong in any task, you need to handle it. That’s where .catch() comes in. It’s like saying “If anything goes wrong in any of the previous tasks, do this”.
task2.catch(handleError);

Here, if anything goes wrong in task1 or task2, handleError will run.

So, in a real example, your code might look like this:

doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => console.log(`Final result is: ${finalResult}`))
.catch(error => console.log(`Oops, an error occurred: ${error}`));

In this example, doSomething, doSomethingElse, and doThirdThing are all tasks you’re doing one after the other. If all tasks are successful, you will print the final result. If anything goes wrong in any task, you will print an error message.

Remember, always use return in your .then() steps if you want to pass something to the next step. If you forget, the next step won’t get the result from the previous step.

Further Reading

Conclusion

Promises are powerful and essential tools in JavaScript for managing asynchronous operations. They might seem daunting at first, but with practice, you’ll find that they can greatly simplify your code, making it more readable and maintainable.

Remember to always handle Promise rejections. Unhandled Promise rejections can lead to hard-to-debug errors. Use .catch() or .finally() to ensure you’re not leaving any Promise rejections unhandled. Happy coding!

Leave a Reply

Your email address will not be published. Required fields are marked *