Home >Web Front-end >JS Tutorial >Everything You Need to Know About JavaScript Promises and How They Work
Modern web development relies heavily on asynchronous activities to enable responsive, interactive applications. Whether it’s retrieving data from an API, reading files, or running timers, these processes must run in the background without freezing the interface. JavaScript offers you a reliable way to handle these jobs. This article covers all you need to know about promises, including basic ideas and advanced features, to develop error-free asynchronous programs.
In this article you will learn about —
What is a Promise?
Why Use Promises?
How Promises Work?
Handling Promises
Chaining Promises
Error Handling in Promises
Advanced Promise Features
JavaScript Execution Flow with Promises (Important)
Converting Promise Chains to Async/Await
Best Practices and Common Mistakes
A Promise in JavaScript is equivalent to making a “promise” to do something in the future. When you make a promise, you are saying, “I promise to give you the results later.” This outcome could be success or failure.
In other words, a promise is an object that reflects the ultimate success (or failure) of an asynchronous operation and its resultant value. It lets you to correlate handlers with the success or failure of an asynchronous action, making your code easier to read and maintainable.
In JavaScript, for instance, time-consuming operations-like retrieving data from a server-were generally accomplished with callbacks. A callback is just a function passed to another function to execute after the task is completed. You might use a callback, for example, to process data when it arrives from a server.
However, when there are complex operations, the use of callbacks get pretty messy. This mess is known as “callback hell,” where one can have a callback within another, and this makes the code unreadable and unmanageable.
Callback Hell Example:
fetchData((data) => { processData(data, (processedData) => { saveData(processedData, (result) => { console.log(result); }); }); });
As shown above, such code becomes increasingly difficult to read and maintain in larger codebases due to its deeply nested structure, often referred to as “callback hell.”
Promises were introduced to address this problem, offering a cleaner and more organized way to handle asynchronous tasks by allowing chaining in a more readable manner.
fetchData((data) => { processData(data, (processedData) => { saveData(processedData, (result) => { console.log(result); }); }); });
This approach flattens the structure and makes the code more readable and maintainable.
Promises in JavaScript can be in one of three states:
Pending: This is the initial step. The promise is still yet to be fulfilled.
Fulfilled: The promise has completed successfully which means it is resolved and has a value.
Rejected: The promise did not complete successfully, and it carries an error message.
Basic Syntax
fetchData() .then(processData) .then(saveData) .then(console.log) .catch(console.error);
In this example, the promise resolves after 1 second with the message “Promise resolved!”. The.then() method is used to handle the resolved value.
The.then()method is used to handle what happens when a promise is successfully completed. It registers functions (callbacks) to run when the promise is fulfilled.
const myPromise = new Promise((resolve, reject) => { setTimeout(() => { resolve("Promise resolved!"); }, 1000); }); myPromise.then(result => console.log(result));
The.catch()method is used to handle what happens when a promise fails. It registers a function (callback) to run when the promise is rejected.
myPromise.then(data => { console.log("Data received:", data); });
The.finally()method lets you run some code after the promise is done, whether it was successful or not.
myPromise.catch(error => { console.error("Error:", error); });
Chaining allows you to perform tasks sequentially by passing the outcome of the previous one. Then proceed to the next.then(). This allows you to handle several asynchronous tasks sequentially.
Example of Chaining:
myPromise.finally(() => { console.log("Cleanup tasks"); });
This example uses each.then()to handle each step in the process, allowing for clear data flow. This allows you to see how the result of one stage is transferred to the next.
Promises simplify error handling by allowing them to pass down the chain to the.catch()method for resolution. This eliminates the need to handle failures at each phase, keeping your code clearer and easier to manage.
Example with Error Propagation:
fetch('https://api.example.com/user') .then(response => response.json()) .then(data => { console.log("Processed data:", data); return processData(data); }) .then(finalResult => { console.log("Final result:", finalResult); }) .catch(error => console.error("Error:", error));
If any step in the promise chain fails, the error will be caught by the.catch()block. This makes it easy to handle issues and keep your code running smoothly.
The Promise.all()method allows you to run several promises simultaneously and wait for them all to complete. If all of the promises are fulfilled, you will receive the results of each one. If any promise fails, it detects the mistake.
fetchData() .then(processData) .then(saveData) .catch(error => console.error("An error occurred:", error));
In this example, if any promise fails, the entire Promise.all() fails.
The Promise.race() method returns the result of the first promise that finishes, whether it succeeds or fails.
fetchData((data) => { processData(data, (processedData) => { saveData(processedData, (result) => { console.log(result); }); }); });
In this example, whichever promise (fetchData1 or fetchData2) completes first will have its result logged to the console.
The Promise.allSettled()method waits for all the promises you give it to be in a successful or failed state and then finish. An array is then returned that has the results of each promise.
fetchData() .then(processData) .then(saveData) .then(console.log) .catch(console.error);
In this example, Promise.allSettled() waits for both fetchData1() and fetchData2() to complete. It then logs the status and result (or error) of each promise. This way, you can see what happened with each promise, regardless of whether they succeeded or failed.
The Promise.any() method waits for the first promise to be resolved correctly from a list of promises. In case at least one promise is resolved, the value will be returned by the Promise.any() method. If all promises are refused, this method will throw an error.
const myPromise = new Promise((resolve, reject) => { setTimeout(() => { resolve("Promise resolved!"); }, 1000); }); myPromise.then(result => console.log(result));
In this example, Promise.any() waits for the first promise to be resolved successfully. The procedure returns the outcome of the first successful promise, in this case promise2with the value ‘Success A’. If all promises are refused, the.catch() block is executed, logging the error message. This strategy is beneficial when you want to receive the result of the first successful promise without having to wait for the rest.
Here’s an example to illustrate this:
myPromise.then(data => { console.log("Data received:", data); });
In this example:
console.log(2) runs first because it's a regular synchronous operation.
console.log (6) runs next because it's also synchronous.
The promise’s.then()runs before the setTimeout callback because promises are microtasks, which have higher priority, hence prints 3.
Finally, the setTimeout callback runs, as it's a macrotask and prints 4.
So always remember, the promise’s.then()executes before the setTimeout callback due to the microtask queue's priority.
In JavaScript, code runs in a specific order: first the synchronous code, then microtasks (like promises), and finally, macrotasks (likesetTimeout).
Here’s one example to explain this:
fetchData((data) => { processData(data, (processedData) => { saveData(processedData, (result) => { console.log(result); }); }); });
In this example, synchronous code runs first, logging 3, 6, 2, 7, and 8. Once the synchronous code finishes, microtasks (the.then() callbacks) are processed, logging 1 and 9. Finally, macrotasks (from setTimeout) execute in order of their delays, logging 21 (0ms) and 13 (10ms). This highlights JavaScript's execution order: synchronous code > microtasks > macrotasks.
When you create a promise, the first call to resolve or reject is the only one that counts. All the other calls are dismissed.
Here’s an example to illustrate this:
fetchData() .then(processData) .then(saveData) .then(console.log) .catch(console.error);
In this example, the promise is resolved with the value 1. The second resolve and the reject calls are ignored because the promise has already been settled with the first resolve.
When you chain promises, each.then() handles a step in the process.
const myPromise = new Promise((resolve, reject) => { setTimeout(() => { resolve("Promise resolved!"); }, 1000); }); myPromise.then(result => console.log(result));
In this example, Promise.resolve(1) starts with a value of 1, but the first .then(() => 2) returns 2 instead. The next .then(3) is ignored, and the value 2 is passed on. The .then((value) => value * 3) multiplies the value by 3, resulting in 6. The .then(Promise.resolve(4)) doesn’t change the value, and finally, .then(console.log) logs 6. This demonstrates how values are passed through the chain, with non-function values being ignored.
myPromise.then(data => { console.log("Data received:", data); });
In this example, we’re chaining multiple.then(),.catch(), and.finally() methods together to show how different stages of promise resolution are handled. Let's break it down:
finally() does not receive an argument:
The finally() block executes clean-up code but doesn't take or pass any values. It’s used to ensure certain code runs regardless of the promise’s outcome.
Returning a value in finally() doesn't affect the promise:
If you return a value in the finally() block, it doesn't affect the promise chain or the final value. It's executed after the promise resolution/rejection but doesn't modify the result.
Throwing an error in finally() causes rejection:
If you throw an error or return a rejected promise in finally(), it will cause the promise chain to reject with the error or rejection reason.
myPromise.catch(error => { console.error("Error:", error); });
OR
myPromise.finally(() => { console.log("Cleanup tasks"); });
Example:
fetchData((data) => { processData(data, (processedData) => { saveData(processedData, (result) => { console.log(result); }); }); });
Async/await is a method for using promises that causes the code to become more like the code written in synchronous mode. The term often used is “syntactic sugar” because it gives a more straightforward and cleaner path of doing asynchronous code.
fetchData() .then(processData) .then(saveData) .then(console.log) .catch(console.error);
You can combine promises with async/await for parallel execution using Promise.all().
const myPromise = new Promise((resolve, reject) => { setTimeout(() => { resolve("Promise resolved!"); }, 1000); }); myPromise.then(result => console.log(result));
Avoid Deep Nesting: Use chaining or async/await to keep code flat and readable.
Always Handle Errors: Make sure every promise chain has a.catch() or a try/catch block.
Use Parallel Execution Wisely: Only use Promise.all() when tasks are independent but need to finish together.
JavaScript promises are one of the best ways to deal with your time-consuming operations, such as, retrieving data on a server. They even help you write cleaner easier-to-maintain code, and the practice of what you have learned will equip you to take full advantage of asynchronous coding. Once you get some hands-on experience and begin handling errors elegantly, promises will become such a huge part of JavaScript.
Thank you for reading! If you find this article helpful, feel free to highlight, clap, leave a comment, or even reach out to me on Twitter/X and LinkedIn as it’s very appreciated and helps keeps content like this free!
The above is the detailed content of Everything You Need to Know About JavaScript Promises and How They Work. For more information, please follow other related articles on the PHP Chinese website!