Async/Await: Clean and Readable Asynchronous Code in JavaScript
Although Promise objects and the .then() / .catch() chaining brought significant quality improvement compared to traditional callbacks, the code was still prone to structural breakdown when dealing with more complex business logic (where there are many branches or variables). The perfect solution to this problem was the async/await syntax introduced in the ES2017 (ES8) standard.
What Exactly Does Async/Await Do?
The async/await is a language construct (syntactic sugar) over Promises. It allows us to write asynchronous code as if it were running sequentially (synchronously), line by line. This does not change the non-blocking operation of the Event Loop running in the background; it merely provides an incredibly clean and intuitive reading layer for the developer.
Practical Application and Try/Catch Error Handling
The rules of usage are simple: We need to place the async keyword before a function. Only within such a function can we use the await keyword, which is placed before asynchronous operations that return a Promise. Error handling can be solved with the traditional try...catch blocks known from synchronous code, which feels much more natural than the .catch() chain.
// The 'async' keyword indicates that the function contains asynchronous operations
async function fetchUserData(userId) {
// Try block for the success path
try {
console.log("Fetching data in progress...");
// 'await' pauses the execution of the function (and only the function!)
// until the fetch Promise resolves
const response = await fetch(`https://api.example.com/users/${userId}`);
// Manual checking of HTTP error status codes
if (!response.ok) {
throw new Error(`Server error! Status code: ${response.status}`);
}
// JSON conversion is also asynchronous, requiring 'await' here as well
const data = await response.json();
console.log("Successful query! User's name:", data.name);
return data; // Returns wrapped in a Promise!
} catch (error) {
// The Catch block catches both network and manually thrown errors
console.error("An error occurred during the process:", error.message);
} finally {
console.log("The fetch attempt has concluded.");
}
}
fetchUserData(1);
Summary
By using async/await, the code's structure remains linear, progressing from top to bottom. We don't have to bother with complex, nested arrow functions. This drastically simplifies debugging, code readability, and maintenance in long-term projects.
Frequently Asked Questions (FAQ)
Can I use await outside of an async function?
Previously you couldn't, but the latest Node.js and browser versions now support the so-called Top-level await feature (ES2022). This means that in the case of ES modules (type="module"), you can also write the await keyword at the top level of the file, which is extremely useful, for example, when initializing database connections.