Asynchronous Functions
Working with the back-end can be confusing; after one asynchronous operation,
you need to make another request to the server using the received data, and so
on a number of times. For example, on a profile page, a user wants to see a list
of friends. The first thing to do is to confirm the user’s access rights to this
page from the back-end. To do this, you need to make a request to
my-api.com/me
. If the back-end allows access, in response you will receive a
unique access token for the protected resources.
const fetchFriends = () => {
return fetch("my-api.com/me").then(token => {
console.log(token);
});
};
Next, you need to request a user profile from my-api.com/profile
, but the
profile is not complete, as it contains only critical information – user ID,
without a list of friends.
const fetchFriends = () => {
return fetch("my-api.com/me")
.then(token => {
return fetch(`my-api.com/profile?token=${token}`);
})
.then(user => {
console.log(user.id);
});
};
Only after that, you can request the list of friends from
my-api.com/users/:userId/friends
.
const fetchFriends = () => {
return fetch("my-api.com/me")
.then(token => {
return fetch(`my-api.com/profile?token=${token}`);
})
.then(user => {
return fetch(`my-api.com/users/${user.id}/friends`);
});
};
fetchFriends()
.then(friends => console.log(friends))
.catch(error => console.error(error));
Not the most readable code, although the operations are relatively simple.
Because you pass handler functions to the then()
method, you get nesting
trees.
Asynchronous functions help get rid of callbacks and nested constructs. At the
same time, they work perfectly in conjunction with the then()
and catch()
methods, as they are guaranteed to return a promise.
const fetchFriends = async () => {
const token = await fetch("my-api.com/me");
const user = await fetch(`my-api.com/profile?token=${token}`);
const friends = await fetch(`my-api.com/users/${user.id}/friends`);
return friends;
};
fetchFriends()
.then(friends => console.log(friends))
.catch(error => console.error(error));
async/await
syntax
Asynchronous functions (async/await) is a convenient way to create
asynchronous code that looks like synchronous. The async/await
syntax is based
on promises, so it does not block the main program flow.
To declare an asynchronous arrow function, add the keyword async
before the
list of parameters. Inside, you can use the await
operator and put, to the
right of it, something what will return a promise. The response.json()
method
also returns a promise, so put await
.
const fetchUsers = async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const users = await response.json();
return users;
};
fetchUsers().then(users => console.log(users));
When the interpreter sees await
, it suspends to execute this function (not the
entire script) and waits until the promise to the right of await
is executed.
As soon as the promise has been executed, the function resumes, and on the line
below you will see the result of the asynchronous operation.
- The
await
operator can only be used in the body of theasync
function. - The
await
operator suspends the function until the promise is executed (fulfilled
orrejected
). - If the promise is
fulfilled
, theawait
operator will return its value. - If the promise is
rejected
, theawait
operator will throw an error. - An asynchronous function always returns a promise, so any return value will be its value.
- If you do not specify a return value, a promise with
undefined
will be returned.
Any function can be asynchronous, be it an object method, class, callback,
declaration or inline function. All of them will be able to use the await
operator and will always return a promise, since they will be asynchronous
functions.
// Function declaration
async function foo() {
// ...
}
// Functional expression
const foo = async function () {
// ...
};
// Arrow function
const foo = async () => {
// ...
};
// Object method
const user = {
async foo() {
// ...
},
};
// Class method
class User {
async foo() {
// ...
}
}
Handling errors
If the result of an asynchronous function (promise) is not used in outer code,
errors are handled in the function body by the try...catch
construct. The
error
parameter value in the catch
block is the error that await
will
generate if the promise is rejected.
const fetchUsers = async () => {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const users = await response.json();
console.log(users);
} catch (error) {
console.log(error.message);
}
};
fetchUsers();
If the result of an asynchronous function (promise) is used in outer (global)
code, that is, outside of other asynchronous functions, errors are handled by
the catch()
method’s callback. The error
parameter value in the catch()
method is the error that await
will generate if the promise is rejected.
const fetchUsers = async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const users = await response.json();
return users;
};
fetchUsers()
.then(users => console.log(users))
.catch(error => console.log(error));
This will not work: await
can only be used in the body of an asynchronous
function.
const fetchUsers = async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const users = await response.json();
return users;
};
// ❌ SyntaxError: await is only valid in async function
const users = await fetchUsers();
If the result of an asynchronous function is used in another asynchronous
function, errors are handled by the try...catch
construct. The error
parameter value in the catch
block is the error that await
will generate if
the promise is rejected.
const fetchUsers = async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const users = await response.json();
return users;
};
const doStuff = async () => {
try {
const users = await fetchUsers();
console.log(users);
} catch (error) {
console.log(error.message);
}
};
doStuff();
Parallel requests
If you need to make multiple requests at the same time, use the async/await
syntax very carefully. In the following example, you will see three
sequential requests, as the execution of the asynchronous function is
suspended when the interpreter sees await
. In addition, request results will
also be parsed sequentially, which will take more time.
const fetchUsers = async () => {
const baseUrl = "https://jsonplaceholder.typicode.com";
const firstResponse = await fetch(`${baseUrl}/users/1`);
const secondResponse = await fetch(`${baseUrl}/users/2`);
const thirdResponse = await fetch(`${baseUrl}/users/3`);
const firstUser = await firstResponse.json();
const secondUser = await secondResponse.json();
const thirdUser = await thirdResponse.json();
console.log(firstUser, secondUser, thirdUser);
};
fetchUsers();
In the Network
tab, you can clearly see that each subsequent request waits for
the previous one to be completed. That is, they are executed sequentially,
which takes more time (total duration of all requests). This is fine when
requests depend on each other, i.e. the next one uses the result of the previous
one.

In our case, they are completely independent, so you need to run them in
parallel. For this, create an array of promises, after which use the
Promise.all()
to wait for their execution. An array of promises is created by
such methods as map()
, filter()
, etc., depending on the task.
const fetchUsers = async () => {
const baseUrl = "https://jsonplaceholder.typicode.com";
const userIds = [1, 2, 3];
// 1. Create an array of promises
const arrayOfPromises = userIds.map(async userId => {
const response = await fetch(`${baseUrl}/users/${userId}`);
return response.json();
});
// 2. Run all promises in parallel and wait for their completion
const users = await Promise.all(arrayOfPromises);
console.log(users);
};
fetchUsers();
With this approach, requests are run in parallel, which saves waiting time for their execution (it is equal to the duration of the "slowest" of them). This technique is only suitable if the requests are independent of each other.

Check this by opening the developer tools in a live example. Also, a button was
added that is used for making requests; a potential error was handled with the
try...catch
construct. This is standard AJAX code using asynchronous
functions.