112

I use custom errors (es6-error) allowing me to handle errors based on their class like so:

import { DatabaseEntryNotFoundError, NotAllowedError } from 'customError';

function fooRoute(req, res) {
  doSomethingAsync()
    .then(() => {
      // on resolve / success
      return res.send(200);
    })
    .catch((error) => {
      // on reject / failure
      if (error instanceof DatabaseEntryNotFoundError) {
        return res.send(404);
      } else if (error instanceof NotAllowedError) {
        return res.send(400);
      }
      log('Failed to do something async with an unspecified error: ', error);
      return res.send(500);
    };
}

Now I'd rather use a switch for this type of flow, resulting in something like:

import { DatabaseEntryNotFoundError, NotAllowedError } from 'customError';

function fooRoute(req, res) {
  doSomethingAsync()
    .then(() => {
      // on resolve / success
      return res.send(200);
    })
    .catch((error) => {
      // on reject / failure
      switch (error instanceof) {
        case NotAllowedError:
          return res.send(400);
        case DatabaseEntryNotFoundError:
          return res.send(404);
        default:
          log('Failed to do something async with an unspecified error: ', error);
          return res.send(500);
      }
    });
}

instanceof doesn't work like that however. So the latter fails.

Is there any way to check an instance for its class in a switch statement?

alextes
  • 1,817
  • 2
  • 15
  • 22

5 Answers5

193

A good option is to use the constructor property of the object:

// on reject / failure
switch (error.constructor) {
    case NotAllowedError:
        return res.send(400);
    case DatabaseEntryNotFoundError:
        return res.send(404);
    default:
        log('Failed to do something async with an unspecified error: ', error);
        return res.send(500);
}

Notice that the constructor must match exactly with the one that object was created (suppose error is an instance of NotAllowedError and NotAllowedError is a subclass of Error):

  • error.constructor === NotAllowedError is true
  • error.constructor === Error is false

This makes a difference from instanceof, which can match also the super class:

  • error instanceof NotAllowedError is true
  • error instanceof Error is true

Check this interesting post about constructor property.

Dmitri Pavlutin
  • 18,122
  • 8
  • 37
  • 41
  • 3
    I like this more than my own answer. This way I won't have to check against strings but can check against the actual object. I'm not sure which would be more sensible from a technical perspective however. – alextes Mar 31 '16 at 11:58
  • 1
    It's easier to write the constructor function than it's name: the autocomplete :). – Dmitri Pavlutin Mar 31 '16 at 11:59
  • 9
    A word of warning to go with this: If you transpile using Babel you may find that the above switch statement always hits the default case. Babel doesn't allow you to subclass built in types like error without using [babel-plugin-transform-builtin-extend](https://github.com/loganfsmyth/babel-plugin-transform-builtin-extend) – Edward Woolhouse Aug 06 '18 at 07:39
  • @EdwardWoolhouse does this apply even now? It's good that i saw you're warning – Moe Elsharif Aug 17 '18 at 15:30
  • 1
    The typescript not understand this syntax :( – Mor Bargig May 20 '22 at 00:35
98

Workaround, to avoid if-else. Found here

switch (true) {
    case error instanceof NotAllowedError: 
        return res.send(400);

    case error instanceof DatabaseEntryNotFoundError: 
        return res.send(404);

    default:
        log('Failed to do something async with an unspecified error: ', error);
        return res.send(500);
}
ya_dimon
  • 3,483
  • 3
  • 31
  • 42
  • 7
    This should be the answer because it's 'backwards compatible' to all flavours/flavors of JS. Using error.constructor will not work in all browsers, and versions of Babel – Nick Mitchell Jan 26 '19 at 06:03
  • 6
    Genius idea ! Exchange condition and case . – Lancer.Yan Dec 27 '19 at 04:24
  • 2
    Typescript doesn't recognize this as a type guard currently: https://github.com/microsoft/TypeScript/issues/37178 – Vsevolod Golovanov Oct 18 '21 at 10:42
  • 2
    How is this better than the if else? I thought the entire point was not repeating `error`? – David Mulder Jan 25 '22 at 16:21
  • @DavidMulder by if-else you should add much more characters and read much more code with curly braces. All will get worse if you need some code for multiple cases (in switch-case out of the box feature). With if-else you should add "||" condition then. So its just comfortable to read and write – ya_dimon Jan 26 '22 at 04:54
2

An alternative to this switch case is to just have a status field in the Error's constructor.

For Example, build your error like so:

class NotAllowedError extends Error {
    constructor(message, status) {
        super(message);
        this.message = message;
        this.status = 403; // Forbidden error code
    }
}

Handle your error like so:

.catch((error) => {
  res.send(error.status);
});
Lachlan Young
  • 1,266
  • 13
  • 15
  • 2
    You're assuming that the caller is a function that handles HTTP communication. Coupling logic of the caller with the invoked function is generally not a good idea. Say the caller has a special case where the response is 200 despite the `NotAllowed` error the function throwing the error now needs to understand the difference and throw the correct `NotAllowedError`. – alextes Aug 22 '18 at 09:31
  • 1
    @AlexTes I am struggling to understand what is wrong with assuming the caller is a function that handles the HTTP communication. What else could a res.send be taking into account the original question? Additionally, you mention a special case around a 200 error. They could add an if in the catch to handle that. My intention here was to give an alternative solution to the original question. Im sorry if im misreading this but it seems like you're applying my answer to an entirely different scope? – Lachlan Young Aug 23 '18 at 06:01
  • 1
    My only point is that it is nicer for `doSomethingAsync` to know nothing about what is calling it. Since it throws the error, and the way the `fooRoute` works depends on what is in the error, `doSomethingAsync` now potentially has to be aware of how `fooRoute` works in order to throw the error with the correct status code. They'd be tied together. – alextes Aug 23 '18 at 20:41
  • 1
    Ah! Thanks for explaining Alex, that's a really good point. I didnt realise you wanted to keep it so seperate, I suppose it's just better practice. – Lachlan Young Aug 23 '18 at 23:39
1

I am unable to write comments so i had to write an answer.

When using constructor object to determine instance type of an object, one should keep in mind the following paragraph from Mozilla developer web docs:

There is nothing protecting the constructor property from being re-assigned or shadowed, so using it to detect the type of a variable should usually be avoided in favor of less fragile ways like instanceof and Symbol.toStringTag for objects, or typeof for primitives.

Another concern when using constructor object is if later you are going to create some new subclasses of already existing classes. Because when checking type this way it doesn't detect objects of subclasses as instances of parent class.

Having said that, this option does provide a nice way to determine object type within switch statement, as long as you aware of risks when doing so.

TLamp
  • 61
  • 3
-1

Why not just use the error name?

class CustomError extends Error {
    constructor(message) {
        super(message);
        this.name = "CustomError";
    }
}

Then:

switch(error.name) {
    case: CustomError.name:
        // Custom error handler.
        break;

    default
        // Default error handler.
        break;
}