Skip to content

Instantly share code, notes, and snippets.

@oakgary
Last active November 26, 2019 10:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oakgary/ed2e2006ad5ecb92af37f72a0b936f57 to your computer and use it in GitHub Desktop.
Save oakgary/ed2e2006ad5ecb92af37f72a0b936f57 to your computer and use it in GitHub Desktop.
Async Error Handling in Node.js

Content

  • Try / Catch
  • Wrapper
  • Higher Order Function
  • Handling on Call
  • UnhandledRejection Event

Try / Catch

Global functions:

const myErroringAsyncFunction = async () => {
  // ... function logic ...
  throw new Error('Test'); // This will normally be an unexpected runtime error.
  // ... function logic ...
}

const myAsyncFunctionWithReturn = async () => {
  // ... function logic ...
  return 'result'
}

Basic

const mainFunction = async () => {

  try {
    await myErroringAsyncFunction()
  } catch (err) {
    // for this case specific error handling
    console.log(err)
  }
}

mainFunction()

This gets messy when your function returns something. Since try/catch is block-scoped, you will not be able to use the result outside of the try block.

const mainFunction = async () => {

  try {
    const result = await myAsyncFunctionWithReturn()
  } catch (err) {
    console.log(err)
  }
  console.log(result); // THIS WILL NOT WORK
}

mainFunction()

Note: A non block scoped variable, declared by var (e.g. var result = ...), would work but thats moving in the wrong direction.

Instead you would declare the variable outside of the try block.

Advanced

const mainFunction = async () => {

  let result = null;
  try {
    result = await myAsyncFunctionWithReturn()
  } catch (err) {
    console.log(err)
  }
  console.log(result);
}

mainFunction()

Wrapper

Global functions:

const myAsyncFunctionWithReturn = async () => {
  // ... function logic ...
  return 'result'
}

const myAsyncFunctionWithReturnWrapper = async () => {
  const returnObj = {};

  try {
    const result = await myAsyncFunctionWithReturn()
    returnObj.result = result;
    throw new Error('Test');
  } catch (err) {
    returnObj.err = err;
    // ... generic error handling ...
    // like logging the error
    console.log(err);
  }

  return returnObj
}

Basic

We get a somewhat cleaner code by wrapping the try/catch blocks of our async functions.

const mainFunction = async () => {

  const { result, err } = await myAsyncFunctionWithReturnWrapper();
  if (err) {
    //specific error handling
  }
  console.log(result);
}

mainFunction()

There is a little problem when calling these functions more than one time tho. We already declared the variables once. Meaning we have to move a step back by defining these variables early and reassign them on the run.

Advanced

const mainFunction = async () => {

  let result = null;
  let err = null;

  ({ result, err } = await myAsyncFunctionWithReturnWrapper());
  if (err) {
    //specific error handling
  }
  console.log({ result });

  ({ result, err } = await myAsyncFunctionWithReturnWrapper());
  if (err) {
    //specific error handling
  }
  console.log({ result });
}

mainFunction()

This already looks like a much cleaner option. But you might run into some trouble when trying to pass more informaiton into the generic error handling. And handling too much in the specific parts will lead to lots of bload code really fast.

Higher Order Function

Global functions:

const myAsyncFunction = async () => {
  // ... function logic ...
  throw new Error('Test');
  // ... function logic ...
}

const myErrorHandlingHOF = (anyAsyncFunc) => {
  return async () => {
    await anyAsyncFunc().catch(err => {
      // ... generic error handling ...
      // like logging the error
      console.log(err);
    })
  }
}

Basic

Let us step up our game with Higher Order Functions as they are a great way to handle errors without much bload.

const mainFunction = async () => {

  const result = await myErrorHandlingHOF(myAsyncFunction)()
  console.log({ result });
}

mainFunction()

Handling on Call

Global functions:

const myAsyncFunction = async () => {
  // ... function logic ...
  throw new Error('Test');
  // ... function logic ...
}

const errHandlingFunc = (err) => {
  // ... generic error handling ...
  // like logging the error
  console.log(err)
}

const errHandlingHOF = (params) => {
  return (err) => {
    // ... generic error handling with custom parameters ...
    console.log({ err, params })
  }
}

Basic

Combining the old schol syntax .catch with the new school await leads to some pretty clean code.

const mainFunction = async () => {

  const result = await myAsyncFunction().catch(errHandlingFunc)
  console.log(result)
}

mainFunction()

Or with non generic error handling logic.

const mainFunction = async () => {

  const local_errHandlingFunc = (err) => {
    // ... specific error handling ...
    console.log({ err })
  }

  const result = await myAsyncFunction.catch(local_errHandlingFunc)()
  console.log({ result })
}

mainFunction()

Advanced

Which we can also combine with a Higher Order Function to pass parameters into our error handling function.

const mainFunction = async () => {

  const result = await myAsyncFunction().catch(errHandlingHOF({ userId: 1 }))
  console.log(result)
}

mainFunction()

Or with non generic error handling logic.

const mainFunction = async () => {

  const local_errHandlingHOF = (params) => {
    return (err) => {
    // ... specific error handling ...
      console.log({ err, params })
    }
  }

  const result = await myAsyncFunction.catch(local_errHandlingHOF({ userId: 1 }))()
  console.log({ result })
}

mainFunction()

UnhandledRejection Event

Global functions:

const myAsyncFunction = async () => {
  // ... function logic ...
  throw new Error('Test');
  // ... function logic ...
}

Basic

This is not a way to handle errors, but to get informed about the unhandled promise rejections you missed and need to fix.

const mainFunction = async () => {
  process.on('unhandledRejection', (reason, promise) => {
    console.log('Unhandled Rejection at:', reason.stack || reason)
    // also send this to your error reporting system
  })

  const result = await myAsyncFunction()

  // Your program will not reach this point. 
  // It will exit due to the unhandledRejection event.
  console.log({ result })
}

mainFunction()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment