Imagine that you have two asynchronous calls like so:
async function getData() {
const users = await fetchUsers();
const categories = await fetchCategories();
}
While the code above will get the job done, you are missing a major opportunity for huge performance gains. These two API calls are run sequentially, meaning that for “fetchCategories” to run, we will wait until “fetchUsers” is completed.
The obvious change here will be to change our code to use Promise.all()
so that these two calls run concurrently.
async function getData() {
const [users, categories] = await Promise.all(
fetchUsers(),
fetchCategories(),
)
}
The Problem
Errors for each of these two API calls are not handled. The (obvious) next step you would think would be to add a try-catch around it, something like the following:
async function getData() {
try {
const [users, categories] = await Promise.all(
fetchUsers(),
fetchCategories(),
)
} catch (err) {
reportToLogger(err);
}
}
That would lead you to believe that any error thrown inside that try-catch
would be handled, which is not the case.
The issue is that if fetchUsers
throws an error, that error would actually be handled. Any other errors thrown after that though, will not. This means that if fetchCategories
throws an exception too for whatever reason, the catch block will not be executed and an “Unhandled Promise Rejection” error will be thrown to the user.
The Solution
We can solve this by using Promise.allSettled
().
async function getData() {
const [userResult, categoryResult] = await Promise.allSettled(
fetchUsers(),
fetchCategories(),
)
}
The Promise.allSettled
method returns a Result Object back. The returned value (or error) is wrapped inside an object which we can use to check if the request was actually fulfilled or rejected. No errors are ever thrown so our code will be fluent and non-nested.
async function getData() {
const [userResult, categoryResult] = await Promise.allSettled(
fetchUsers(),
fetchCategories(),
)
if (userResult.status === 'rejected') {
const error = userResult.reason
// handle error
} else {
const users = userResult.value
}
if (categoryResult.status === 'rejected') {
const error = categoryResult.reason
// handle error
}
}
Additional Resources
with Promise.all you can still handle promises inside the function itself it has a different use case.
You can also opt to assign the promises to variables and await them after all the requests are initiated. This gives you some more control with error handling. For example: you don’t have to wait for the categories if the user isn’t set.
const userPromise = fetchUser();
const categoriesPromise = fetchCategories();
const userResult = await userPromise;
const categoriesResult = await categoriesPromise;