Async/аwait Functions in JavaScript
Introdcution
Asynchronous programming can make your code run more efficiently and avoid blocking the main thread, but it can also make it more complex and harder to reason about.
Async/await functions are a way to simplify asynchronous code in JavaScript and serve as syntactical sugar wrapped around promises, making it easier to read and write complex asynchronous logic.
Prerequisites
Before diving into async
and await
, you need to have a solid understanding of promises.
As we mentioned in our previous article, promises are a way to handle asynchronous results in a more synchronous-like fashion. They have a then
method that can be used to register a callback function that will be called when the promise is resolved and a catch
method that can be used to register a callback function that will be called when the promise is rejected.
We can refresh our knowledge with the following example of using both methods to handle the result of an asynchronous function that returns a promise:
myAsyncFunction()
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error(error);
});
The Syntax
The async
keyword is used to define an asynchronous function that returns a promise, allowing us to use the await
keyword inside the function. The await
keyword "waits" for a promise to be resolved before moving on to the next line of code. Here's an example of an asynchronous function that returns a resolved promise after a delay using setTimeout
:
async function delayedLog() {
await new Promise((resolve) => setTimeout(resolve, 1000));
console.log("1 second has passed");
}
delayedLog();
the delayedLog()
function is asynchronous and returns a resolved promise after one second. Then, the await
keyword is used to wait for the promise to be resolved before logging a message.
Another example would be fetching data from an API:
async function getData() {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
const data = await response.json();
console.log(data);
} catch (error) {
console.log(error);
}
}
getData();
We're using the fetch()
function to make a GET
request to the specified API endpoint. The await
keyword is used to wait for the response to be returned before parsing the data as JSON.
Similarly, we can use async
and await
when handling user input asynchronously:
async function handleSubmit(event) {
event.preventDefault();
const input = document.querySelector("input");
const value = await new Promise((resolve) => {
setTimeout(() => {
resolve(input.value);
}, 1000);
});
console.log(`You typed: ${value}`);
}
document.querySelector("form").addEventListener("submit", handleSubmit);
In this example, handleSubmit
handles the form submission event, and the await
keyword waits for the user input to be resolved from the promise, which is resolved after 1 second.
Also, let's take a look at a more elaborate example of using async/await in an e-commerce web application to handle user registration and login:
// Common function for making POST requests
const makeRequest = async (url, body) => {
try {
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify(body),
headers: { 'Content-Type': 'application/json' }
});
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
return data;
} catch (error) {
console.error(error);
}
}
// Function for user registration
const registerUser = async (email, password) => {
// Make the POST request to register the user
const data = await makeRequest('/api/register', { email, password });
// Log success message
console.log('User registered successfully');
// Return the token
return data.token;
}
// Function for user login
const login = async (email, password) => {
// Make the POST request to login the user
const data = await makeRequest('/api/login', { email, password });
// Log welcome message
console.log(`Welcome ${data.name}!`);
// Return the token
return data.token;
}
In this example, we have two async functions, registerUser
and login
that are handling the registration and login of the user by making a POST
request to the server using the fetch
function. The await
waits for the response to be returned before parsing the data as JSON. If there's an error, it will be caught by the catch block and logged in the console.
Keep in mind that this is just a simplified example, and in real-world implementation, we should also handle the token received and store it securely in the browser or a backend service, and also validate the user's input before making the requests.
Conclusion
While async/await functions provide a convenient way to handle asynchronous code, they are not suitable for every situation. Aside from the summary, we'll also include some of the limitations and nuances:
Async/await is a powerful tool for handling asynchronous code, making it more readable and maintainable.
The technique is built on top of Promises, so understanding promises is a must.
Always use try-catch to handle errors and avoid chaining multiple await statements.
Async/await is suitable for handling promises,
setTimeout
, orfetch
API, but not for synchronous code.Async/await can be slower than Promises, especially when used in tight loops or when the promise is already resolved, so it's important to optimize its usage.
The technique isn't a silver bullet and its usage must be optimized and weighed based on the specific use case.
Mastering it takes time and practice, but it can be a game-changer when it comes to making your code more readable and maintainable.