In JavaScript, async and await are a syntax for writing asynchronous code that is easier to read and maintain than previous alternatives (such as callbacks and promises).
Introduced in ECMAScript 2017, async/await allows us to write code sequentially, instead of nesting callbacks or chaining promises.
Internally, async/await is based on promises. In fact, then
/catch
and async
/await
are functionally equivalent.
But async/await provides a more convenient syntax to use, which is syntactic sugar for simplifying our lives.
Another advantage of async/await is that it handles errors in a much simpler way. With async/await, you can use the try/catch
structure (which is more familiar and easier to understand for developers).
Async/Await Syntax
The async/await syntax is based on two keywords:
- async is placed before the function to indicate that it contains asynchronous code.
- await is placed before any operation that returns a promise to indicate that the code must wait for the promise to resolve before continuing.
Async
The async
keyword is used to declare an asynchronous function. A function marked with async
always returns a promise.
- If the function returns a value, the promise is resolved with that value.
- If the function throws an exception, the promise is rejected with that exception.
async function myFunction() {
return 'Hello, world';
}
myFunction().then(console.log); // 'Hello, world'
In the example above, myFunction
is an asynchronous function that returns a promise that resolves with the value 'Hello, world'
.
Await
The await
keyword is used inside an async
function to wait for the resolution of a promise.
await
pauses the execution of the async
function until the promise resolves or rejects.
async function myFunction() {
let value = await Promise.resolve('Hello, world');
console.log(value); // 'Hello, world'
}
myFunction();
In this example, await
is used to wait for the promise to resolve, and then the resulting value is printed.
Basic Example
Let’s see it with a simple example,
async function getData() {
//function that simulates returning a promise
const response = await functionThatReturnsPromise();
console.log(response);
}
In this example,
- The
getData
function is asynchronous. - The
functionThatReturnsPromise
is a function that simulates returning a promise (it is the response from a network request). - We use the
await
keyword to wait for a promise to resolve before continuing with the next line of code. - After the promise resolves, we can do whatever we want with
response
.
This would be equivalent to this code, without using async and await:
function getData() {
// Call the function that returns a promise
functionThatReturnsPromise()
.then((response) => {
// Handle the response when the promise resolves
console.log(response);
})
.catch((error) => {
// Handle errors if the promise is rejected
console.error(error);
});
}
Which as we see is quite a hassle 😅
Internal Promises
An async
function always returns a promise. This promise is resolved with the value returned from the function.
If an exception is thrown inside the async
function, the promise is rejected with that exception.
async function successFunction() {
return 'Success';
}
async function errorFunction() {
throw new Error('Something went wrong');
}
successFunction().then(console.log); // 'Success'
errorFunction().catch(console.error); // 'Error: Something went wrong'
In successFunction
,
- The value
'Success'
is wrapped in a promise that resolves. - In
errorFunction
, the thrown exception is wrapped in a promise that rejects.
Handling Errors
To handle errors in async
functions, you can use try
/catch
instead of .catch()
as you would with promises.
async function getData() {
try {
let response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error('Network response was not ok');
}
let data = await response.json();
console.log(data);
} catch (error) {
console.error('Error:', error);
}
}
getData();
In this example, if an error occurs at any point within the try
block, the control flow moves to the catch
block, where the error is handled.
Chaining Asynchronous Operations
You can chain multiple asynchronous operations within an async
function.
async function processData() {
let data = await getDataFromAPI();
let processed = await processData(data);
console.log(processed);
}
async function getDataFromAPI() {
let response = await fetch('https://api.example.com/data');
return await response.json();
}
async function processData(data) {
// Data processing
return data.map(item => item * 2);
}
processData();
In this example, processData
uses await
to wait for the results of getDataFromAPI
and processData
in a sequence of operations.
Concurrent Functions
If you need to run multiple asynchronous operations in parallel, you can use Promise.all
with await
.
async function performOperations() {
let promise1 = fetch('https://api.example.com/data1');
let promise2 = fetch('https://api.example.com/data2');
let responses = await Promise.all([promise1, promise2]);
let data = await Promise.all(responses.map(res => res.json()));
console.log(data);
}
performOperations();
In this case,
- The two
fetch
requests are executed in parallel. Promise.all
waits for both to complete before continuing.