If you have worked with JavaScript for a while, you already know it runs code line by line. But real-world apps often need to wait for something, an API call, a file read, a timer, without freezing the entire program. That’s where asynchronous JavaScript comes in.
Older techniques like callbacks and even Promises in JavaScript often lead to complex, hard-to-read code. Async await in JavaScript solves this by letting you write asynchronous logic in a clean, synchronous style.
In this guide, you will learn what async await is in JavaScript, how async await in JavaScript works, when to use it, and the common mistakes to avoid.
Table of Contents
What is async/await in JavaScript?
The async and await keywords were introduced in ECMAScript 2017 (ES2017) to make writing asynchronous JavaScript simpler, cleaner, and more readable.
What async in JavaScript Does
When you mark a function as async:
- The function always returns a Promise
- Any non-Promise value is automatically wrapped in a resolved Promise
- Errors thrown inside the function become rejected Promises
This makes async functions a predictable, promise-based structure.
What await in JavaScript Does
When you use the await keyword inside an async function:
- It pauses the execution of that function until the Promise settles
- It waits for the Promise to resolve and returns the resolved value
- If the Promise rejects, await throws an error right at that line
- The pause affects only that async function, not the rest of your JavaScript program
- It allows your asynchronous code to look sequential and easy to read
In short, await lets you write asynchronous code in a natural, top-to-bottom flow without using .then() chains or nested callbacks.
Syntax of async/await in JavaScript
In JavaScript, the async/await syntax makes asynchronous code look and behave more like synchronous code. It is built on top of Promises, but gives you a cleaner and more readable way to write async logic.
The async Keyword
When you put the async keyword before a function, JavaScript automatically makes that function return a Promise, no matter what you return inside it.
async function myFunction() {
return "Hello";
}
// Returns: Promise<string>
myFunction().then(console.log); // Hello
Even if you return a simple value, JavaScript wraps it in a resolved Promise.
2. The await Keyword
The await keyword can be used only inside an async function.
It pauses the function execution until the Promise on its right-hand side resolves or rejects.
async function getData() {
const result = await someAsyncOperation();
console.log(result);
}
Using await makes your async function “wait” at that point without blocking the rest of the program. JavaScript continues running other tasks in the event loop.
How async and await in JavaScript Work
Even though async and await make your code look synchronous, JavaScript still runs it asynchronously under the hood. The process involves Promises and the event loop.
Here’s what actually happens:
- When you call an async function, JavaScript immediately returns a Promise, even before the function finishes.
- Inside an async function, when JavaScript hits an await, it pauses that function’s execution.
- While the function is paused, the event loop continues running other tasks; your whole program does not freeze.
When the awaited Promise resolves:
- Execution resumes from the paused line
- The resolved value is returned and assigned
- If the Promise rejects, an error is thrown at that exact line, which is why try…catch works perfectly with async/await.
JavaScript Async Await Example
Let’s look at a quick example to gain a better understanding.
Output:
- “Step 1” prints immediately.
- The await tells JavaScript to pause example() but keep running the rest of the program.
- “This runs while waiting…” logs next, showing the event loop is still active.
- After one second, the Promise resolves, and “Step 2: Done” logs.
Replacing JavaScript Promises with Async and Await for Cleaner Code
Here, the first example uses .then() and .catch() to handle a Promise, requiring chained callbacks to process the result. The second example achieves the same with async and await, making the code look synchronous. await pauses execution until the Promise resolves, and try…catch handles errors.
We will start with simulating fetching data using setTimeout wrapped in a Promise.
Output:
Now, let’s rewrite the exact same logic using async and await in JavaScript.
Output:
- getData() returns a Promise that resolves after one second.
- In showData(), the await keyword pauses execution until getData() finishes, then assigns the result to result.
- The rest of the program can still run while waiting, only the showData() function is paused.
Convenient, isn’t it? Once you learn to write asynchronous code using async and await keywords, it’s hard to go back to nested callbacks or .then() chains.
Difference Between Promises and Async Await
Even though async and await are built on top of Promises, they are not the same thing. Promises provide the underlying mechanism for handling asynchronous operations, while async/await gives you a cleaner and more readable way to write that logic. Think of async/await as a modern, more intuitive way to work with Promises, not a replacement.
Here’s a quick comparison to help you understand when to use which:
Promises vs Async/Await: Quick Comparison
| Feature |
Promises |
Async/Await |
| Syntax Style |
Uses .then() and .catch() chains |
Looks synchronous, easier to read |
| Error Handling |
.catch() must be added to the chain |
try…catch works naturally |
| Readability |
Harder to follow with multiple async steps |
Very readable, top-down flow |
| Debugging |
Stack traces can be messy |
Cleaner stack traces |
| Use Case |
Great for running tasks in parallel (Promise.all) |
Great for sequential async logic |
| Underlying Mechanism |
Direct use of Promises |
Built on top of Promises |
| Learning Curve |
Slightly harder for beginners |
Easier due to synchronous-style syntax |
JavaScript async/await with Multiple Tasks
One of the great things about async/await is how easy it is to implement multiple asynchronous operations. The trick is to know when to run them sequentially and when to run them in parallel.
1. Sequential Execution
Sometimes, you would want tasks to run in order because each one depends on the result of the other. In which case, you can simply add multiple await calls one after the other. Straightforward isn’t it? Let’s look at an example.
Output:
Here, the second task only starts after the first one finishes, taking about 2 seconds in total.
2. Parallel Execution with Promise.all()
If tasks do not depend on each other, you can run them at the same time to save time:
Both tasks run together, so the total time is just about 1 second.
Other Promise Utilities
- Promise.allSettled(): waits for all Promises to finish, no matter if they resolve or reject.
- Promise.race(): returns the first Promise to settle (resolve or reject).
- Promise.any(): returns the first Promise that resolves successfully (ignores rejections until all fail).
Example with Promise.allSettled():
This can be handy when you want every operation to run and simply record which ones failed.
A Common Pitfall: await in Loops
Using await directly inside a loop will make the loop run sequentially, which can be slow if you do not need that. Instead, collect Promises and await them together:
Knowing when to go sequential and when to go parallel can make a huge difference in performance, especially when working with network calls or file operations.
Advanced JavaScript async/await Patterns
Once you have gained a deep understanding of how the async and await keywords in JavaScript work, there are several more advanced patterns that you can use to solve complex problems. Let’s have a look at some of them in detail. And do not panic if you are not able to understand these at first, as they are very complex and advanced patterns aimed at experienced developers.
1. Async Iterators with for await…of
Sometimes, when you are working with APIs, especially, data comes in pieces. Async iterators let you handle these piece by piece as they become available.
Output:
Here, the loop waits for each new value without blocking the rest of the program.
2. Cancellation with AbortController
When you need to end asynchronous tasks before completion, you can use AbortController to do so.
Note: AbortController works only with APIs that support an AbortSignal (like fetch).
3. Throttling and Batching
If you have hundreds of tasks to run, say, fetching data for a list of users, running them all at once can overwhelm your system or hit rate limits. A batching approach can help:
Here, two tasks run at a time until all are done. This keeps resource usage under control.
JavaScript async await Error Handling
Regardless of how clean your code looks with async and await in JavaScript, there are many things that can still go wrong: a network might fail, a file might be missing, or maybe some unexpected data causes an exception. This is why error handling is paramount if you do not want your code or application to throw unexpected errors or even crash.
You probably already know that error handling is done through chaining .catch() for Promises. In async/await try…catch is the most common and efficient way to handle errors.
Here’s a simple example:
Output:
- If the Promise resolves, the code after await runs normally.
- If the Promise rejects, control jumps to the catch block, just like an exception in synchronous code.
You can also still use .catch() with an async function if you prefer:
This approach is shorter, but it’s better suited for handling specific operations in isolation rather than wrapping entire functions.
JavaScript async await Common Mistakes
Even experienced developers make mistakes while implementing async/await in JavaScript, mostly due to its asynchronous nature. Most of these issues are not bugs in JavaScript itself; they come from misunderstanding how asynchronous code behaves. Let’s have a look at some of the common mistakes and how to avoid them.
1. Forgetting await
If you call an async function without await, you get a Promise instead of its result.
Output:
Fix: Add await when you actually need the resolved value.
Edit the codelab for yourself and see how the result changes.
2. Using await inside forEach
forEach does not work well with await because it does not wait for async callbacks to finish.
This runs all tasks in parallel and does not wait for them before moving on.
Fix: Use a for…of loop for sequential waits, or map to Promises and use Promise.all for parallel execution.
3. Ignoring Errors
If a Promise rejects and you do not catch it, you can get an “unhandled promise rejection” warning.
Fix: Always wrap your awaited code in try…catch or attach a .catch() to the Promise.
4. Mixing Callbacks and Async/Await
Combining callback-based functions with async/await often leads to confusion. If possible, wrap callbacks in a Promise before using them with await.
Output:
Conclusion
Mastering async/await is more than just learning syntax; it’s about thinking in terms of concurrency, efficiency, and clean code structure. The more you practice, the more natural it becomes to spot where asynchronous patterns can make your applications faster and more responsive.If you want to go deeper into JavaScript concepts, from fundamentals to advanced topics like async patterns, modules, and performance optimization, consider joining our complete Full-stack Development course. It’s designed with practical examples, real-world projects, and hands-on exercises to help you master the MERN stack.
Async and Await in JavaScript – FAQs
1. What are the states of a JavaScript Promise?
A JavaScript Promise has three states:
- Pending: the initial state (neither resolved nor rejected).
- Fulfilled: the operation completed successfully (resolve).
- Rejected: the operation failed (reject).
2. What is the difference between Promise and async/await?
- Promise: uses .then() and .catch() to handle results.
- async/await: syntactic sugar that makes asynchronous code look synchronous.
3. How do you handle errors in JavaScript Promises?
In Promises: handle errors using .catch()
In async/await: handle errors using try...catch
// Promise with .catch()
fetch("/invalid-url")
.then(res => res.json())
.catch(error => console.error("Error:", error));
// async/await with try...catch
async function getData() {
try {
let res = await fetch("/invalid-url");
let data = await res.json();
} catch (err) {
console.error("Error:", err);
}
}
4. What is the difference between Promise.all, Promise.race, and Promise.allSettled?
- Promise.all: waits for all Promises to resolve (or rejects if one fails).
- Promise.race: returns the result of the first settled Promise (success or failure).
- Promise.allSettled: waits for all Promises to finish, returning both results and errors.
5. Can a JavaScript Promise return multiple values?
No, a Promise in JavaScript resolves with only one value. However, if you need multiple values, you can return them inside an array or an object.