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
What is a Promise?
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.
Why Use Promises?
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.
Promise-Based Approach:
fetchData((data) => { processData(data, (processedData) => { saveData(processedData, (result) => { console.log(result); }); }); });
This approach flattens the structure and makes the code more readable and maintainable.
How Promises Work?
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.
Handling Promises
Using .then() for Success Handling
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));
Using .catch() for Error Handling
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); });
Using .finally() for Cleanup
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 Promises
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.
Error Handling in Promises
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.
Advanced Promise Features
1. Promise.all() for Parallel Execution
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.
2. Promise.race() for Fastest Promise
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.
3. Promise.allSettled() for Handling All Outcomes
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.
4.Promise.any() for Resolving with the First Successful Promise
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.
JavaScript Execution Flow with Promises (Important)
1. Promises in JavaScript run in the microtask queue, which gets priority over macrotasks like setTimeout.
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.
2. Promise Execution Order and the Microtask Queue with Multiple .then() Calls
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.
3. Multiple Resolve and Reject Calls in a Promise: Only the First One Matters
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.
4. Chaining Promises and Handling Values in Sequential .then() Calls
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.
5. Promise Chain with .catch() and .finally() Handling
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"); });
- The order of then() and catch() matters The .then() and .catch() can be invoked in any order, but they will always return the promise’s final state. When a promise is handled by .catch(), any subsequent .then() will receive the final value.
Example:
fetchData((data) => { processData(data, (processedData) => { saveData(processedData, (result) => { console.log(result); }); }); });
Converting Promise Chains to Async/Await
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);
Combining Promises with Async/Await
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));
Best Practices and Common Mistakes
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.
Conclusion
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!

JavaScript's application in the real world includes front-end and back-end development. 1) Display front-end applications by building a TODO list application, involving DOM operations and event processing. 2) Build RESTfulAPI through Node.js and Express to demonstrate back-end applications.

The main uses of JavaScript in web development include client interaction, form verification and asynchronous communication. 1) Dynamic content update and user interaction through DOM operations; 2) Client verification is carried out before the user submits data to improve the user experience; 3) Refreshless communication with the server is achieved through AJAX technology.

Understanding how JavaScript engine works internally is important to developers because it helps write more efficient code and understand performance bottlenecks and optimization strategies. 1) The engine's workflow includes three stages: parsing, compiling and execution; 2) During the execution process, the engine will perform dynamic optimization, such as inline cache and hidden classes; 3) Best practices include avoiding global variables, optimizing loops, using const and lets, and avoiding excessive use of closures.

Python is more suitable for beginners, with a smooth learning curve and concise syntax; JavaScript is suitable for front-end development, with a steep learning curve and flexible syntax. 1. Python syntax is intuitive and suitable for data science and back-end development. 2. JavaScript is flexible and widely used in front-end and server-side programming.

Python and JavaScript have their own advantages and disadvantages in terms of community, libraries and resources. 1) The Python community is friendly and suitable for beginners, but the front-end development resources are not as rich as JavaScript. 2) Python is powerful in data science and machine learning libraries, while JavaScript is better in front-end development libraries and frameworks. 3) Both have rich learning resources, but Python is suitable for starting with official documents, while JavaScript is better with MDNWebDocs. The choice should be based on project needs and personal interests.

The shift from C/C to JavaScript requires adapting to dynamic typing, garbage collection and asynchronous programming. 1) C/C is a statically typed language that requires manual memory management, while JavaScript is dynamically typed and garbage collection is automatically processed. 2) C/C needs to be compiled into machine code, while JavaScript is an interpreted language. 3) JavaScript introduces concepts such as closures, prototype chains and Promise, which enhances flexibility and asynchronous programming capabilities.

Different JavaScript engines have different effects when parsing and executing JavaScript code, because the implementation principles and optimization strategies of each engine differ. 1. Lexical analysis: convert source code into lexical unit. 2. Grammar analysis: Generate an abstract syntax tree. 3. Optimization and compilation: Generate machine code through the JIT compiler. 4. Execute: Run the machine code. V8 engine optimizes through instant compilation and hidden class, SpiderMonkey uses a type inference system, resulting in different performance performance on the same code.

JavaScript's applications in the real world include server-side programming, mobile application development and Internet of Things control: 1. Server-side programming is realized through Node.js, suitable for high concurrent request processing. 2. Mobile application development is carried out through ReactNative and supports cross-platform deployment. 3. Used for IoT device control through Johnny-Five library, suitable for hardware interaction.


Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

AI Hentai Generator
Generate AI Hentai for free.

Hot Article

Hot Tools

SecLists
SecLists is the ultimate security tester's companion. It is a collection of various types of lists that are frequently used during security assessments, all in one place. SecLists helps make security testing more efficient and productive by conveniently providing all the lists a security tester might need. List types include usernames, passwords, URLs, fuzzing payloads, sensitive data patterns, web shells, and more. The tester can simply pull this repository onto a new test machine and he will have access to every type of list he needs.

WebStorm Mac version
Useful JavaScript development tools

ZendStudio 13.5.1 Mac
Powerful PHP integrated development environment

Safe Exam Browser
Safe Exam Browser is a secure browser environment for taking online exams securely. This software turns any computer into a secure workstation. It controls access to any utility and prevents students from using unauthorized resources.

MinGW - Minimalist GNU for Windows
This project is in the process of being migrated to osdn.net/projects/mingw, you can continue to follow us there. MinGW: A native Windows port of the GNU Compiler Collection (GCC), freely distributable import libraries and header files for building native Windows applications; includes extensions to the MSVC runtime to support C99 functionality. All MinGW software can run on 64-bit Windows platforms.