In Chapter 11, we learned about Promises for handling asynchronous code, often involving complex .then()
chains. async
and await
keywords, introduced in ES2017, provide a much cleaner, more readable, and arguably safer way to write Promise-based code, making it look almost exactly like synchronous code.
1. The async
Keyword
Any function declared with the async
keyword automatically returns a Promise.
JavaScript
// An async function
async function getUserData() {
// If the function returns a value, that value is wrapped in a resolved Promise.
return 'User data is ready';
}
getUserData().then(data => console.log(data)); // Output: User data is ready
2. The await
Keyword
The await
keyword can only be used inside an async
function. It pauses the execution of the async
function until the Promise it precedes is settled (either resolved or rejected).
The value of the await
expression is the resolved value of the Promise.
Rewriting fetch
with async/await
Compare this to the .then()
chain from Chapter 11. The code now reads naturally from top to bottom.
JavaScript
async function fetchAndDisplayUser() {
const userId = 1;
const url = `https://jsonplaceholder.typicode.com/users/${userId}`;
try {
// 1. Await the network response (Response object is resolved)
const response = await fetch(url);
// Check for HTTP errors
if (!response.ok) {
throw new Error(`Failed to fetch user: ${response.status}`);
}
// 2. Await the JSON parsing (Actual JavaScript object is resolved)
const user = await response.json();
// 3. Process the result (synchronous-looking code)
console.log(`User Name: ${user.name}`);
console.log(`User Email: ${user.email}`);
return user;
} catch (error) {
// Catches errors from fetch() or the manual throw new Error()
console.error('Error in fetchAndDisplayUser:', error.message);
}
}
fetchAndDisplayUser();
3. Error Handling with try...catch
One of the biggest advantages of async/await
is the ability to use the standard try...catch
block to handle Promise rejections. Any error thrown (or any rejected Promise that is await
ed) will be caught by the catch
block, just like synchronous errors.
4. Concurrent Operations (Promise.all
)
Sometimes, you need to execute multiple asynchronous operations at the same time and wait for all of them to finish. Using await
sequentially is too slow if the operations are independent.
Promise.all(iterable)
takes an array of promises and returns a single Promise that resolves when all of the promises in the iterable have resolved, or rejects immediately if any of them reject.
JavaScript
async function fetchAllData() {
const todosPromise = fetch('https://jsonplaceholder.typicode.com/todos/1').then(res => res.json());
const postsPromise = fetch('https://jsonplaceholder.typicode.com/posts/1').then(res => res.json());
// Wait for BOTH promises to resolve simultaneously
const [todo, post] = await Promise.all([todosPromise, postsPromise]);
console.log('Todo data:', todo);
console.log('Post data:', post);
}
fetchAllData(); // Much faster than awaiting each one sequentially
By using async/await
, you write cleaner, more maintainable asynchronous code, making complex data fetching and coordination tasks much easier to manage.