Reputation: 9493
Assume we have an action that runs on user login (express, node). This is the code that works, written using a lot of callbacks:
checkIfEmailAndPasswordAreSet(email, password, (error, response) => {
if (error) return errorResponse(403, 'validation error', error)
findUserByEmail(email, (error, user) => {
if (error) return errorResponse(500, 'db error', error)
if (!user) return errorResponse(403, 'user not found')
checkUserPassword(user, password, (error, result) => {
if (error) return errorResponse(500, 'bcrypt error', error)
if (!result) return errorResponse(403, 'incorrect password')
updateUserLastLoggedIn(user, (error, response) => {
if (error) return errorResponse(500, 'db error', error)
generateSessionToken(user, (error, token) => {
if (error) return errorResponse(500, 'jwt error', error)
return successResponse(user, token)
})
})
})
})
})
I want to rewrite this code using async/await and avoid callback hell. How to do that?
The first attempt could look like that:
try {
await checkIfEmailAndPasswordAreSet(email, password)
const user = await findUserByEmail(email)
if (!user) throw new Error('user not found')
const result = await checkUserPassword(user, password)
if (!result) throw new Error('incorrect password')
await updateUserLastLoggedIn(user)
const token = await generateSessionToken(user)
return successResponse(user, token)
} catch (e) {
// How to handle the error here?
}
I want to keep the proper error handling, that is if the error was thrown in checkUserPassword
method, I want the response to contain info about this. What should I write in the catch method?
For example, I could wrap every instruction into it's own try / catch block like that:
try {
let user, result
try {
await checkIfEmailAndPasswordAreSet(email, password)
} catch (error) {
throw new Error('This error was thrown in checkIfEmailAndPasswordAreSet')
}
try {
user = await findUserByEmail(email)
} catch (error) {
throw new Error('This error was thrown in findUserByEmail')
}
if (!user) throw new Error('user not found')
...
} catch (error) {
return errorResponse(error)
}
But this code.. probably that's not a callback hell, but I would call it try/catch hell. It takes at least 2 times more rows that the original old fashioned code with callbacks. How to rewrite it to be shorter and take an advantage of async/await?
Upvotes: 10
Views: 4125
Reputation: 664930
There is no easy handling of individual errors with try
/catch
.
I would probably write some helper functions to facilitate simple error throwing:
function error(code, msg) {
return e => {
e.status = code;
e.details = msg;
throw e;
};
}
function ifEmpty(fn) {
return o => o || fn(new Error("empty"));
}
and then use standard promise methods:
try {
await checkIfEmailAndPasswordAreSet(email, password)
.catch(error(403, 'validation error'));
const user = await findUserByEmail(email)
.then(ifEmpty(error(403, 'user not found')), error(500, 'db error', error));
await checkUserPassword(user, password)
.then(ifEmpty(error(403, 'incorrect password')), error(500, 'bcrypt error'));
await updateUserLastLoggedIn(user)
.catch(error(500, 'db error'));
const token = generateSessionToken(user)
.catch(error(500, 'jwt error'));
return successResponse(user, token);
} catch(err) {
errorResponse(err.status, err.details, err);
}
Upvotes: 14