JavaScript - Promises

JavaScript Promises are a powerful tool for managing asynchronous operations. They represent a value that may be available now, in the future, or never. Promises are a modern alternative to traditional callback-based approaches, making code easier to read and maintain.

1. What is a Promise?

A Promise is an object representing the eventual completion (or failure) of an asynchronous operation. It is used to handle asynchronous tasks like fetching data, reading files, or waiting for a timer to expire.

2. States of a Promise

Promises have three states:

Pending: The initial state, neither fulfilled nor rejected.

Fulfilled: The operation completed successfully, and the promise has a result.

Rejected: The operation failed, and the promise has a reason for failure.

3. Creating a Promise

You can create a promise using the Promise constructor, which takes a function with two parameters: resolve and reject.

Example:

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

    let condition = true; // Change to false to test rejection

    if (condition) {

        resolve("The promise was fulfilled!");

    } else {

        reject("The promise was rejected!");

    }

});

// Logging the promise

console.log(myPromise);

4. Using .then() and .catch()

Consuming a Promise:

Use .then() to handle the resolved value and .catch() to handle errors.

Example:

myPromise

    .then((message) => {

        console.log(message); // Logs: "The promise was fulfilled!"

    })

    .catch((error) => {

        console.error(error); // Logs: "The promise was rejected!"

    });

5. Chaining Promises

Promises can be chained for sequential asynchronous operations. Each .then() returns a new promise, allowing multiple operations to be linked.

Example:

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

    resolve(10);

});

fetchData

    .then((data) => {

        console.log(`Step 1: ${data}`);

        return data * 2; // Pass to the next .then()

    })

    .then((data) => {

        console.log(`Step 2: ${data}`);

        return data + 5;

    })

    .then((data) => {

        console.log(`Step 3: ${data}`);

    })

    .catch((error) => {

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

    });

6. The .finally() Method

The .finally() method is called regardless of whether the promise is resolved or rejected. It's useful for cleanup tasks.

Example:

fetchData

    .then((data) => {

        console.log(`Data: ${data}`);

    })

    .catch((error) => {

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

    })

    .finally(() => {

        console.log("Operation completed.");

    });

7. Handling Multiple Promises

Promise.all()

Waits for all promises to resolve and returns an array of results. If any promise is rejected, the entire operation fails.

const promise1 = Promise.resolve("First");

const promise2 = Promise.resolve("Second");

const promise3 = Promise.resolve("Third");

 

Promise.all([promise1, promise2, promise3])

    .then((results) => {

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

    })

    .catch((error) => {

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

    });

Promise.race()

Returns the result of the first promise to resolve or reject.

const slowPromise = new Promise((resolve) => setTimeout(() => resolve("Slow"), 2000));

const fastPromise = new Promise((resolve) => setTimeout(() => resolve("Fast"), 1000));

Promise.race([slowPromise, fastPromise])

    .then((result) => {

        console.log(result); // Logs: "Fast"

    });

8. Common Mistakes and Best Practices

Mistakes:

Forgetting to handle rejections: Always include a .catch() block.

Returning undefined in .then(): Ensure to return values for chaining.

Mixing callbacks and promises: Stick to one pattern for cleaner code.

Best Practices:

Use async/await for cleaner syntax when handling promises.

Use Promise.allSettled() to handle arrays of promises where failures should not interrupt the process.

Handle exceptions properly to avoid unhandled rejections.

9. Conclusion

Promises are an integral part of modern JavaScript, simplifying the management of asynchronous tasks. By mastering promises, you can write cleaner, more reliable, and maintainable code.

Key Takeaways:

Use .then() for resolved promises and .catch() for errors.

Chain promises for sequential operations.

Handle multiple promises with Promise.all() or Promise.race().

Leverage .finally() for cleanup tasks.