Async/Await in JavaScript
Introduced in ECMAScript 2017 (ES8), async/await provides a more comfortable, synchronous-looking syntax for working with Promises. It abstracts away the boilerplate of .then() and .catch() chains, making asynchronous code much easier to read and maintain.
1. async Functions
The async keyword is used to declare an asynchronous function. It guarantees that the function will always return a Promise. If the function explicitly returns a non-Promise value, JavaScript will automatically wrap it in a resolved Promise.
async function greet() {
return "Hello, world!";
}
// Equivalent to:
// function greet() {
// return Promise.resolve("Hello, world!");
// }
greet().then(console.log); // "Hello, world!"
2. The await Expression
The await keyword can only be used inside an async function (or at the top level of a module). It makes JavaScript wait until the Promise settles and returns its result.
async function fetchUser() {
// Execution pauses here until the fetch Promise resolves
const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
// Execution pauses again here
const user = await response.json();
return user;
}
Behind the scenes, await does not block the main thread; it yields execution back to the event loop, allowing other scripts or events to run while waiting.
3. Error Handling
Instead of chaining .catch(), you handle errors in async/await using standard try...catch blocks. This allows you to handle synchronous and asynchronous errors uniformly.
async function getUserData() {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error("Failed to fetch data:", error.message);
} finally {
console.log("Fetch attempt finished.");
}
}
4. Parallel vs Sequential Execution
A common pitfall is accidentally running independent asynchronous tasks sequentially rather than in parallel.
Sequential (Slower)
async function getAuthorsAndBooks() {
// Waits 2 seconds
const authors = await fetchAuthors();
// Waits another 2 seconds
const books = await fetchBooks();
return { authors, books }; // Total time: 4 seconds
}
Parallel (Faster)
To run them concurrently, start the Promises before awaiting them, or use Promise.all().
async function getAuthorsAndBooksFast() {
const [authors, books] = await Promise.all([
fetchAuthors(),
fetchBooks()
]);
return { authors, books }; // Total time: 2 seconds (time of the longest request)
}
5. Top-Level await
Historically, await could only be used inside an async function. Modern environments (ES2022+ in ES Modules) support top-level await. This is incredibly useful for module initialization, such as database connections or dynamic imports.
// db.js (ES Module)
const connection = await connectToDatabase();
export { connection };
// translations.js
const strings = await import(`./translations/${navigator.language}.js`);
Summary
asyncmakes a function return a Promise.awaitmakes a function wait for a Promise.- Use
try...catchfor error handling. - Use
Promise.all()to run independent tasks in parallel. - Top-level
awaitsimplifies module initialization.