Async and Await in JavaScript
JavaScript is single-threaded and asynchronous by nature, which means tasks can run in the background without blocking the main thread. Prior to ES2017, asynchronous code was mainly handled using callbacks and promises. These methods worked, but could often lead to less readable and harder-to-maintain code.

To make asynchronous code more readable, async and await were introduced in ES2017 (ECMAScript 2017). They allow developers to write asynchronous code that looks synchronous, making it easier to understand and maintain. This article will break down how async and await work and how they simplify working with asynchronous code in JavaScript.
What is async and await?
async: Theasynckeyword is used to declare an asynchronous function. It allows the function to implicitly return a promise, even if you're not returning a promise explicitly. The function will always return a promise object.await: Theawaitkeyword can only be used inside anasyncfunction. It pauses the execution of the function until the promise resolves (or rejects), and then resumes execution. It is used to "await" the result of an asynchronous operation.
Together, async and await provide a clean and more intuitive way to handle asynchronous code.
How to Use async and await
Example of Basic async Function
async function greet() {
return "Hello, World!";
}
greet().then((message) => console.log(message));
// Output: "Hello, World!"
In this example:
- The
greetfunction is declared asasync, which means it automatically returns a promise. - Even though we are returning a string (
"Hello, World!"), JavaScript wraps it in a promise. - We can use
.then()to get the value when the promise resolves.
Example of await in Action
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve("Data fetched!"), 2000); // Simulate a network request
});
}
async function getData() {
const result = await fetchData();
console.log(result); // Logs: "Data fetched!" after 2 seconds
}
getData();
In this example:
- The
fetchDatafunction returns a promise that resolves after 2 seconds. - In
getData, we use theawaitkeyword to pause execution untilfetchDatacompletes. - Once
fetchDataresolves, the value ("Data fetched!") is assigned toresult, and the program continues.
How async and await Simplify Promises
To understand the improvement that async/await brings, consider how promises were handled before:
Promise Chaining Example
fetchData()
.then((result) => {
console.log(result);
return processData(result);
})
.then((processedData) => {
console.log(processedData);
})
.catch((error) => {
console.error("Error:", error);
});
This code works, but it can become difficult to read and maintain with more complex logic, especially with deeply nested .then() blocks.
The Same Logic Using async and await
async function getData() {
try {
const result = await fetchData();
console.log(result);
const processedData = await processData(result);
console.log(processedData);
} catch (error) {
console.error("Error:", error);
}
}
getData();
With async/await, the code looks more like synchronous code and is easier to read. Instead of chaining .then() and .catch(), we can write asynchronous code in a top-to-bottom fashion, using try/catch for error handling.
Error Handling with async/await
Just like promises, async/await provides a mechanism to handle errors that may occur during asynchronous operations. Errors inside an async function can be caught using a try/catch block.
Example of Error Handling:
async function getData() {
try {
const result = await fetchData();
console.log(result);
} catch (error) {
console.error("Error:", error);
}
}
getData();
In this example:
- If the
fetchDatapromise is rejected, the code inside thetryblock will throw an error, which will be caught by thecatchblock. - This is a cleaner and more intuitive way to handle errors compared to the
.catch()method with promises.
Chaining Multiple await Statements
You can use multiple await statements inside an async function. The function will wait for each asynchronous operation to finish before moving to the next line.
Example of Multiple await Statements:
async function fetchDataAndProcess() {
const data = await fetchData();
console.log("Data fetched:", data);
const processedData = await processData(data);
console.log("Processed data:", processedData);
}
fetchDataAndProcess();
In this example:
- The
awaitkeyword ensures that each asynchronous operation (fetching and processing data) completes before moving on to the next.
Parallel Execution with async/await
By default, await pauses the execution of the function until the promise is resolved. However, sometimes you may want to run multiple asynchronous operations in parallel instead of waiting for each to finish one by one.
To achieve parallel execution, you can use Promise.all() with async/await.
Example of Parallel Execution:
async function fetchDataInParallel() {
const [data1, data2] = await Promise.all([fetchData(), fetchOtherData()]);
console.log(data1, data2);
}
fetchDataInParallel();
In this example:
- Both
fetchDataandfetchOtherDatarun in parallel, meaning they are initiated at the same time. - Using
Promise.all(), we can await both promises and get the results once both are resolved.
Returning Values from Async Functions
Since async functions return a promise, you can use the .then() method to handle the returned value, or use await to wait for the result when calling another async function.
Example:
async function fetchValue() {
return 42;
}
fetchValue().then((value) => {
console.log("The value is:", value); // Logs: "The value is: 42"
});
Alternatively, you can use await to get the result:
async function getValue() {
const value = await fetchValue();
console.log("The value is:", value); // Logs: "The value is: 42"
}
getValue();
Comparison: async/await vs Promises
| Feature | Promises | async/await |
|---|---|---|
| Syntax | .then() and .catch() chaining | Looks like synchronous code |
| Error handling | .catch() method | try/catch blocks |
| Readability | Can lead to nesting (promise chains) | Cleaner and more linear code |
| Execution control | Harder to follow the flow of execution | Easier to follow due to top-down flow |
| Parallel execution | Use Promise.all() | Use Promise.all() in async/await |
When to Use async/await
You should use async/await when:
- You want cleaner and more readable asynchronous code.
- You want to avoid promise chains and nested
.then()calls. - You need a more intuitive way to handle asynchronous operations, especially in complex workflows.
- You want to use standard error-handling mechanisms (try/catch) for async code.
async and await provide a modern, intuitive way to handle asynchronous operations in JavaScript. They build on top of promises, allowing developers to write asynchronous code that looks synchronous. This makes your code more readable, maintainable, and easier to work with, especially when dealing with complex asynchronous workflows.
Key points:
asyncfunctions return a promise and make it easier to work with asynchronous operations.awaitpauses the execution until the promise is resolved, making asynchronous code appear synchronous.- Use
try/catchfor error handling insideasyncfunctions. - Use
Promise.all()for running asynchronous tasks in parallel.
Mastering async/await is crucial for writing clean, modern JavaScript code, especially when working with APIs, databases, or any other asynchronous tasks.