Async/Await – Asynchronous Programming Made Easy
Asynchronous programming is an essential part of modern JavaScript development, especially when working with network requests, file systems, or other time-consuming operations. With the introduction of Async/Await in ES2017, handling asynchronous code was significantly simplified. In this article, I will explain how Async/Await works and how it makes your code more readable and maintainable.
What is Async/Await?
Async/Await is a syntax extension for Promises that allows you to write asynchronous code as if it were synchronous. It is based on using the keywords async and await to control the execution of asynchronous functions.
asyncdeclares a function as asynchronous and ensures that the function always returns a Promise.awaitpauses execution within anasyncfunction until a Promise is resolved.
Why Use Async/Await?
Before Async/Await, asynchronous code was primarily handled with Callbacks or Promises. While Promises already represent an improvement over nested callbacks (Callback Hell), they can still become confusing with complex sequences of asynchronous operations. Async/Await allows simplifying this code and making it more readable.
Basics of Usage
Defining an asynchronous function:
async function fetchData() {
// Function contains asynchronous code
}
Using the await keyword:
async function fetchData() {
const response = await fetch('https://swapi.dev/api/people/1/');
const data = await response.json();
console.log(data);
}
In this example:
- The function
fetchDatais declared withasync. awaitis used to wait for the resolution of the Promises fromfetchandresponse.json().- The code after the
awaitis only executed when the Promise is fulfilled.
A Simple Example
Let’s compare code with Promises and with Async/Await.
With Promises:
function fetchUser() {
return fetch('https://swapi.dev/api/people/1/')
.then(response => response.json())
.then(person => {
console.log(person);
})
.catch(error => {
console.error('Error:', error);
});
}
fetchUser();
With Async/Await:
async function fetchUser() {
try {
const response = await fetch('https://swapi.dev/api/people/1/');
const person = await response.json();
console.log(person);
} catch (error) {
console.error('Error:', error);
}
}
fetchUser();
The Async/Await code is linear and looks similar to synchronous code, making it easier to understand.
Error Handling with try...catch
Since await pauses the execution of the function, we can use normal try...catch blocks to handle errors.
async function fetchData() {
try {
const response = await fetch('https://swapi.dev/api/people/1/');
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const { name } = await response.json();
console.log(name);
} catch (error) {
console.error('Error:', error);
}
}
Parallelizing Asynchronous Operations
If you have multiple asynchronous operations that are independent of each other, you can execute them in parallel to reduce the total time.
Sequential Execution (slower):
async function getData() {
const data1 = await fetchData1();
const data2 = await fetchData2();
return [data1, data2];
}
Parallel Execution (faster):
async function getData() {
const promise1 = fetchData1();
const promise2 = fetchData2();
const data1 = await promise1;
const data2 = await promise2;
return [data1, data2];
}
Or even better with Promise.all:
async function getData() {
const [data1, data2] = await Promise.all([fetchData1(), fetchData2()]);
return [data1, data2];
}
Important Notes
-
Only within
asyncfunctions: Theawaitkeyword can only be used within functions declared withasync.// Wrong (after ES2017) const data = await fetchData(); // SyntaxError // Correct async function getData() { const data = await fetchData(); } -
Use of
awaitoutside and insideasyncfunctions: With the introduction of Top-Levelawaitin the ECMAScript 2022 standard, theawaitkeyword can now also be used outside of functions declared withasync, as long as it occurs in an ES module.// Correct in ES modules (from ECMAScript 2022) const data = await fetchData(); // Still correct async function getData() { const data = await fetchData(); } -
asyncfunctions always return a Promise: Even if you don’t explicitly return a Promise, the return value is wrapped in a Promise.async function add(a, b) { return a + b; } add(2, 3).then(result => console.log(result)); // 5 -
Errors propagate automatically: If an error occurs within an
asyncfunction and is not caught, the returned Promise is rejected.
Application Example: Fetching Data from an API
async function getPerson(id) {
try {
const response = await fetch(`https://swapi.dev/api/people/${id}/`);
if (!response.ok) {
throw new Error(`Person not found: ${id}`);
}
const { name, birth_year} = await response.json();
console.log(`${name}: ${birth_year}`);
} catch (error) {
console.error('Error:', error.message);
}
}
getPerson(1);
Async/Await with Arrow Functions
Async/Await can also be used with arrow functions:
const fetchData = async () => {
const data = await getData();
console.log(data);
};
Conclusion
Async/Await revolutionizes the way we write asynchronous code in JavaScript. It allows us to write asynchronous processes in synchronous syntax, making the code easier to read and maintain. By combining Async/Await with other modern JavaScript features, you can develop more efficient and cleaner applications.
Try out Async/Await in your next project and experience the difference in code quality and readability!