r0b
Homepage

All posts

Creating custom JavaScript errors

Creating custom errors in JavaScript can be very useful. If you want to handle those errors in a catch block you can use the instanceof operator to check for that specific error. And with TypeScript, you can then safely use the fields and methods on your custom error.

Another benefit of custom errors is that you can provide your own constructor which lets you pass more information to the error. Which is information you can use in an error handler.

Custom JavaScript errors are almost as simple as creating an Error subclass, but there are two things you can do to make them even more useful.

Set the name

The first thing to enhance custom errors is to set this.name inside the constructor. This means your error name will appear in stack traces rather than the generic Error:.

Re-capture the stack trace

An unfortunate side-effect of creating a custom error is that your custom constructor will appear in the stack trace. This is because your super call is technically creating the error and the JavaScript VM records that in the generated stack trace. This isn't useful and makes stack traces messier.

Removing this makes it easier to get to the code that's thrown the error. You can do this with the Error.captureStackTrace static method.

An example

To demonstrate this, let's create a custom error that captures extra information for a http API, so we can throw a desired http status and an error identifier.

class ApiError extends Error {
  constructor(status, apiCode) {
    super(`There was an error with your API request: "${apiCode}"`)
    this.name = 'ApiError'
    this.status = status
    this.apiCode = apiCode
    Error.captureStackTrace(this, ApiError)
  }
}

That's all you need, you could use it like this:

throw new ApiError(404, 'notFound')

// or

throw new ApiError(400, 'login.emailNotProvided')

and this will produce a stack trace like this:

/◦◦◦/custom-errors/main.js:4
  throw new ApiError(404, "notFound");
  ^

ApiError: There was an error with your API request: "notFound"
    at runApp (/◦◦◦/custom-errors/main.js:4:9)
    at main (/◦◦◦/custom-errors/main.js:12:3)
    at Object.<anonymous> (/◦◦◦/custom-errors/main.js:15:1)
    ◦◦◦ {
  status: 404,
  apiCode: 'notFound'
}

See examples/custom-errors for the exact source code.

The output shows that it has namespaced the error with ApiError: which is from us setting this.name. The first line of the stack trace is not ApiError's constructor but runApp which was the method which threw the error. Because of this, it now shows you the exact line of code that threw the error in the excerpt.

You can also see that status and apiCode have been stored on the error.

Catching errors

To complete this post, here's an example of catching an ApiError and using the extra fields.

const express = require('express')
const app = express()

// From above
class ApiError extends Error {
  /* ... */
}

// A route which results in an error
app.get('/', (req, res, next) => {
  next(new ApiError(404, 'notFound'))
})

// An Express error handler, more info at:
// http://expressjs.com/en/guide/error-handling.html
app.use((error, req, res, next) => {
  console.error('Handled error', error)

  if (error instanceof ApiError) {
    res.status(error.status).send({ apiCode: error.apiCode })
  } else {
    res.status(500).send({ apiCode: 'unknownError' })
  }
})

app.listen(3000)

This shows throwing an ApiError in an ExpressJs context and handling it with an error middleware. It checks for an ApiError with the instanceof operator, and then safely uses the fields on it to generate a http response. The logic also nicely allows a generic http 500 error to be sent when unknown errors are thrown.

Bonus: static methods

When using custom errors another pattern I've used is to add static methods to easily create common errors. The does bring back the stack trace issue, so another Error.captureStackTrace is needed.

To demonstrate this, lets add some common errors to ApiError:

class ApiError extends Error {
  static notFound() {
    return new ApiError(404, 'general.notFound').trimStack()
  }
  static unauthorized() {
    return new ApiError(401, 'general.unauthorized').trimStack()
  }

  // Same as above
  constructor(/* ... */) {
    /* ... */
  }

  trimStack() {
    Error.captureStackTrace(this, ApiError)
    return this
  }
}

I found the utility function trimStack helps keeps these static methods easier to read and understand.

Now you can quickly create common errors with throw ApiError.notFound(), which is easier to read and hopefully leads to less mistakes. For the full source code see See examples/custom-errors.