Understanding JavaScript Promises

JavaScript promises are a powerful way to handle asynchronous operations. Before the introduction of promises, callbacks were the primary method to handle asynchronous tasks. However, callbacks can lead to “callback hell,” where nested callbacks become difficult to manage and read. Promises provide a cleaner, more readable way to manage asynchronous operations.

What is a Promise?

A promise in JavaScript represents the eventual completion (or failure) of an asynchronous operation and its resulting value. It is an object that can be in one of three states:

  • Pending: Initial state, neither fulfilled nor rejected.
  • Fulfilled: Operation completed successfully.
  • Rejected: Operation failed.

Creating a Promise

To create a promise, use the Promise constructor, which takes a function with two parameters: resolve and reject. These functions control the promise’s state.

const myPromise = new Promise((resolve, reject) => { let success = true; // Simulate an operation if (success) { resolve("Operation successful!"); } else { reject("Operation failed."); }
});
Enter fullscreen mode Exit fullscreen mode

Using Promises

Promises have then, catch, and finally methods to handle the result.

  • then: Called when the promise is fulfilled.
  • catch: Called when the promise is rejected.
  • finally: Called regardless of the promise’s outcome.
myPromise .then((message) => { console.log(message); // "Operation successful!" }) .catch((error) => { console.error(error); // "Operation failed." }) .finally(() => { console.log("Operation completed."); });
Enter fullscreen mode Exit fullscreen mode

Chaining Promises

One of the main advantages of promises is chaining. You can chain multiple then calls to perform a sequence of asynchronous operations.

const firstPromise = new Promise((resolve) => { setTimeout(() => resolve("First promise resolved"), 1000);
}); firstPromise .then((result) => { console.log(result); // "First promise resolved" return new Promise((resolve) => { setTimeout(() => resolve("Second promise resolved"), 1000); }); }) .then((result) => { console.log(result); // "Second promise resolved" }) .catch((error) => { console.error(error); });
Enter fullscreen mode Exit fullscreen mode

Handling Multiple Promises

JavaScript provides utility methods to handle multiple promises concurrently: Promise.all, Promise.race, Promise.allSettled, and Promise.any.

  • Promise.all: Waits for all promises to resolve or any to reject.
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve) => setTimeout(resolve, 100, 'foo')); Promise.all([promise1, promise2, promise3]).then((values) => { console.log(values); // [3, 42, 'foo']
});
Enter fullscreen mode Exit fullscreen mode
  • Promise.race: Resolves or rejects as soon as one of the promises resolves or rejects.
const promise1 = new Promise((resolve) => setTimeout(resolve, 500, 'one'));
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'two')); Promise.race([promise1, promise2]).then((value) => { console.log(value); // "two"
});
Enter fullscreen mode Exit fullscreen mode
  • Promise.allSettled: Waits for all promises to settle (either resolve or reject).
const promise1 = Promise.resolve(42);
const promise2 = Promise.reject('error');
const promise3 = new Promise((resolve) => setTimeout(resolve, 100, 'foo')); Promise.allSettled([promise1, promise2, promise3]).then((results) => { results.forEach((result) => console.log(result.status)); // "fulfilled" // "rejected" // "fulfilled"
});
Enter fullscreen mode Exit fullscreen mode
  • Promise.any: Waits for any promise to resolve.
const promise1 = Promise.reject('error1');
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'foo')); Promise.any([promise1, promise2]).then((value) => { console.log(value); // "foo"
}).catch((error) => { console.error(error); // AggregateError: All promises were rejected
});
Enter fullscreen mode Exit fullscreen mode

Common Mistakes with Promises

While promises are powerful, they can also be a source of confusion and errors. Here are some common mistakes developers make when working with promises:

1. Not Returning a Promise in a then Handler

For chaining to work correctly, you must return a promise inside a then handler. Failing to do so breaks the chain.

firstPromise .then((result) => { console.log(result); new Promise((resolve) => setTimeout(resolve, 1000, 'Second promise resolved')); // Missing return }) .then((result) => { console.log(result); // This will log undefined });
Enter fullscreen mode Exit fullscreen mode

2. Catching Errors Too Early

Catching an error early in a chain can prevent downstream then handlers from being aware of the error.

firstPromise .then((result) => { throw new Error('Something went wrong'); }) .catch((error) => { console.error(error); // Caught too early }) .then(() => { console.log('This will still run'); });
Enter fullscreen mode Exit fullscreen mode

To avoid this, place error handling at the end of the chain or as close to the specific operation as possible.

3. Creating Unnecessary Promises

It’s common to wrap synchronous operations in promises unnecessarily, which adds complexity.

const promise = new Promise((resolve) => { resolve('Hello, World!');
});
promise.then((message) => console.log(message));
Enter fullscreen mode Exit fullscreen mode

Instead, just use the value directly:

Promise.resolve('Hello, World!').then((message) => console.log(message));
Enter fullscreen mode Exit fullscreen mode

4. Forgetting to Handle Rejections

Always handle rejections to prevent unhandled promise rejections, which can cause issues in your application.

const promise = new Promise((_, reject) => { reject('Error occurred');
});
promise.then((message) => console.log(message)); // No catch handler
Enter fullscreen mode Exit fullscreen mode

Always add a catch block:

promise .then((message) => console.log(message)) .catch((error) => console.error(error));
Enter fullscreen mode Exit fullscreen mode

5. Mixing Promises and Async/Await

While promises and async/await are both ways to handle asynchronous code, mixing them can lead to confusion.

async function fetchData() { const data = await fetch('https://api.example.com/data'); data.then((result) => console.log(result)); // Unnecessary promise
}
Enter fullscreen mode Exit fullscreen mode

Stick to one approach:

async function fetchData() { const data = await fetch('https://api.example.com/data'); console.log(await data.json());
}
Enter fullscreen mode

Conclusion

Promises are an essential part of modern JavaScript, providing a robust way to handle asynchronous operations. By understanding how to create, use, and manage promises, you can write cleaner, more maintainable code. Avoiding common mistakes will help you leverage promises effectively and prevent potential pitfalls. Whether you’re handling a single asynchronous task or coordinating multiple concurrent operations, promises are a powerful tool in your JavaScript toolkit.

This post was initially published on my blog. Check out the original source using the link below:

Learn the essentials of JavaScript promises, including their creation, usage, and common pitfalls. Understand how to handle asynchronous operations.

favicon rahulvijayvergiya.hashnode.dev


Discover more from Coursity

Subscribe to get the latest posts sent to your email.

Leave a Comment

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

Discover more from Coursity

Subscribe now to keep reading and get access to the full archive.

Continue reading

Scroll to Top