Just wanted to preemptively say that I am familiar with async/await and promises in JavaScript so no need to link me to some MDN pages for that.
I have a function to fetch user details and display it on the UI.
async function someHttpCall() {
throw 'someHttpCall error'
}
async function fetchUserDetails() {
throw 'fetchUserDetails error'
}
function displayUserDetails(userDetails) {
console.log('userDetails:', userDetails)
}
async function fetchUser() {
try {
const user = await someHttpCall()
try {
const details = await fetchUserDetails(user)
returndisplayUserDetails(details)
} catch (fetchUserDetailsError) {
console.log('fetching user error', fetchUserDetailsError)
}
} catch (someHttpCallError) {
console.log('networking error:', someHttpCallError)
}
}
It first makes HTTP call via someHttpCall
and if it succeeds then it proceeds to fetchUserDetails
and it that succeeds as well then we display the details on Ui via returndisplayUserDetails
.
If someHttpCall
failed, we will stop and not make fetchUserDetails
call. In other words, we want to separate the error handling for someHttpCall
and it’s data handling from fetchUserDetails
The function I wrote is with nested try catch
blocks which doesn't scale well if the nesting becomes deep and I was trying to rewrite it for better readability using plain then
and catch
This was my first atttempt
function fetchUser2() {
someHttpCall()
.then(
(user) => fetchUserDetails(user),
(someHttpCallError) => {
console.log('networking error:', someHttpCallError)
}
)
.then(
(details) => {
displayUserDetails(details)
}, //
(fetchUserDetailsError) => {
console.log('fetching user error', fetchUserDetailsError)
}
)
}
The problem with this is that the second then
will run i.e. displayUserDetails
even with someHttpCall
failing. To avoid this I had to make the previous .catch
blocks throw
so this is the updated version
function fetchUser2() {
someHttpCall()
.then(
(user) => fetchUserDetails(user),
(someHttpCallError) => {
console.log('networking error:', someHttpCallError)
throw someHttpCallError
}
)
.then(
(details) => {
displayUserDetails(details)
}, //
(fetchUserDetailsError) => {
console.log('fetching user error', fetchUserDetailsError)
}
)
}
However now the second catch will get called as a result of the throw. So when the someHttpCall
failed, after we handled the someHttpCallError
error, we would enter this block (fetchUserDetailsError) => { console.log('fetching user error', fetchUserDetailsError) }
which is not good since fetchUserDetails
never gets called so we shouldn't need to handle fetchUserDetailsError
(I know someHttpCallError
became fetchUserDetailsError
in this case)
I can add some conditional checks in there to distinguish the two errors but it seems less ideal. So I am wondering how I can improve this by using .then
and .catch
to achieve the same goal here.
I am wondering how I can improve this by using
.then
and.catch
to achieve the same goal here
You don't get to avoid the nesting if you want to replicate the same behaviour:
function fetchUser2() {
return someHttpCall().then(
(user) => {
return fetchUserDetails(user).then(
(details) => {
return displayUserDetails(details)
},
(fetchUserDetailsError) => {
console.log('fetching user error', fetchUserDetailsError)
}
)
},
(someHttpCallError) => {
console.log('networking error:', someHttpCallError)
throw someHttpCallError
}
)
}
(The exact equivalent to try
/catch
would use .then(…).catch(…)
instead of .then(…, …)
, but you might not actually want that.)
The function I wrote is [nested] which doesn't scale well if the nesting becomes deep and I was trying to rewrite it for better readability […]
For that, I would recommend to combine await
with .catch()
:
async function fetchUser() {
try {
const user = await someHttpCall().catch(someHttpCallError => {
throw new Error('networking error', {cause: someHttpCallError});
});
const details = await fetchUserDetails(user).catch(fetchUserDetailsError => {
throw new Error('fetching user error', {cause: fetchUserDetailsError});
});
return displayUserDetails(details);
} catch (someError) {
console.log(someError.message, someError.cause);
}
}
(The cause
option for Error
is still quite new, you might need a polyfill for that)