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

*爱你&永不变心* 提交于 2021-02-18 09:52:32

问题


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.


回答1:


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.




回答2:


What do you mean by:

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'.

Error object can't be instance of another error object. Or you are saying that errors from module-a or module-b when doing something like err instanceof CustomError are returning different results? Having in mind that instanceof tests presence of constructor.prototype in object's prototype chain both errors from those modules should return true by code that you posted, when tested againt CustomError.

Can you show how you are creating those errors in those modules?

This is because both modules have their own copy of the JS file containing the CustomError constructor, which are both run independently.

Again, im confused by this statement. What do you mean by both modules have their own copy of something? Lets have small example:

// custom-error.js
'use strict'

function CustomError () {}

CustomError.prototype = Object.create(Error.prototype)
CustomError.prototype.constructor = CustomError

module.exports = CustomError

// module-a.js
const CustomError = require('./custom-error')
const err = new CustomError(...)
module.exports = err

// module-b.js
const CustomError = require('./custom-error')
const err = new CustomError(...)
module.exports = err

// dummy file to require those
const CustomError = require('./custom-error')
const errA = require('./module-a')
const errB = require('./module-b')

First both errA and errB should be an instanceof CustomError:

console.log(errA instanceof CustomError) // yields true
console.log(errB instanceof CustomError) // yields true

cunstructor property of errA and errB that will be found in prototype chain, should have reference that points to same function object CustomError

console.log(errA.constructor === errB.constructor) // yields true

Lets introduce also filtered catch example:

const Promise = require('bluebird')

Promise.resolve()
.then(() => {
  throw errA
})
.catch(CustomError, err => {
  console.log('instance of CustomError catched', err)
  throw errB
})
.catch(CustomError, err => {
  console.log('instance of CustomError catched again', err)
})

Results:

instance of CustomError catched [Error]
instance of CustomError catched again [Error]

And last thing, what do you mean in your example when you say deep npm dependencies? This CustomError thing is your module or 3rd party lib? The fact that is 3rd party module or not, that should not change anything.



来源:https://stackoverflow.com/questions/41587865/using-instanceof-on-objects-created-with-constructors-from-deep-npm-dependenci

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!