Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using `instanceof` on objects created with constructors from deep npm dependencies

Background:

I have an npm module that I have common error handling code in, including a custom error:

function CustomError () { /* ... */ }
CustomError.prototype = Object.create(Error.prototype);
CustomError.prototype.constructor = CustomError;
module.exports = CustomError;

I have some other modules (let's call them 'module-a' and 'module-b') which both depend on the error handling module.

I also have some code with uses Bluebirds "filtered catch" functionality:

doSomething
.catch(CustomError, () => { /* ... */ });

The issue:

After some debugging I've discovered (somewhat obviously in hindsight) that errors created in 'module-a' are not instances of errors created by 'module-b'. This is because both modules have their own copy of the JS file containing the CustomError constructor, which are both run independently.

I would rather not have to resort to my current solution, which is basically:

CustomError.isCustomError = e => e.constructor.toString() === CustomError.toString();

and then:

doSomething
.then(CustomError.isCustomError, () => { /* ... */ });

This is clearly flimsy, and will fall apart if the versions fall out of sync.

So...

Is there some way to ensure that 'module-a' and 'module-b' both use the same instance of the constructor? Or another, less fragile solution.

like image 754
phenomnomnominal Avatar asked Jan 11 '17 10:01

phenomnomnominal


1 Answers

This is actually a problem in browsers too, when you have an iframe it gets its own copy of, for example, the Array constructor (making instanceof useless).

The solution for a custom constructor is to duck-type it. Here are some potential solutions with pros and cons.

  1. Check the constructor name. Pro: simple, well-supported. Con: better pick a fairly unique name to avoid false positives and forget about sub-classing it.

  2. Check the properties of the object (e.g. has both 'foo' and 'bar and 'foo' is a function). Pro: mostly fool-proof. Cons: fragile: this check may randomly break if you refactor your custom error class, relatively expensive.

  3. (Recommended) Add a property/method. This is how a number of libraries (for example, moment.js) handle this problem.

Code example:

CustomError.prototype._isCustomError = true;
var isCustomError = function isCustomError(obj) {
  return obj instanceof CustomError || Boolean(obj._isCustomError);
};

module.exports = {
  CustomError,
  isCustomError
};

This is more or less exactly how moment detects whether or not a given object is a moment object.

like image 155
Jared Smith Avatar answered Oct 25 '22 14:10

Jared Smith