Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

async / await proper error handling

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?

like image 584
Kasheftin Avatar asked Jan 27 '18 12:01

Kasheftin


People also ask

Which is the best standard approach on error handling for async function?

Best Practices for writing great async/await code A try/catch block can be used to handle asynchronous errors in an async function. Alluding to the fact that an async function always return a Promise, one can opt to use a . catch() in place of a whole try/catch block.

Does await throw error?

If a promise resolves normally, then await promise returns the result. But in the case of a rejection, it throws the error, just as if there were a throw statement at that line. In real situations, the promise may take some time before it rejects. In that case there will be a delay before await throws an error.

How would you handle errors for async code in node JS?

If we want to handle the error for asynchronous code in Node. js then we can do it in the following two manners. Handle error using callback: A callback function is to perform some operation after the function execution is completed. We can call our callback function after an asynchronous operation is completed.

Should I always use try catch with async await?

No. You don't need to use try/catch in every async/await. You only need to do it at the top level. In this case your main function which you are already doing.


1 Answers

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);
}
like image 157
Bergi Avatar answered Sep 27 '22 22:09

Bergi