Javascript 在使用构造函数从深度npm依赖项创建的对象上使用'instanceof' 背景:

Javascript 在使用构造函数从深度npm依赖项创建的对象上使用'instanceof' 背景:,javascript,node.js,npm,Javascript,Node.js,Npm,我有一个npm模块,其中包含常见的错误处理代码,包括一个自定义错误: function CustomError () { /* ... */ } CustomError.prototype = Object.create(Error.prototype); CustomError.prototype.constructor = CustomError; module.exports = CustomError; 我还有一些其他模块(我们称它们为'module-a'和'module-b'),它们

我有一个npm模块,其中包含常见的错误处理代码,包括一个自定义错误:

function CustomError () { /* ... */ }
CustomError.prototype = Object.create(Error.prototype);
CustomError.prototype.constructor = CustomError;
module.exports = CustomError;
我还有一些其他模块(我们称它们为'module-a''module-b'),它们都依赖于错误处理模块

我还有一些使用蓝鸟“过滤捕获”功能的代码:

doSomething
.catch(CustomError, () => { /* ... */ });
问题是: 经过一些调试后,我发现(事后看来有点明显)在'module-a'中创建的错误不是由'module-b'创建的错误的实例。这是因为两个模块都有自己的JS文件副本,其中包含
CustomError
构造函数,这两个模块都独立运行

我不想求助于我目前的解决方案,基本上是:

CustomError.isCustomError = e => e.constructor.toString() === CustomError.toString();
然后:

doSomething
.then(CustomError.isCustomError, () => { /* ... */ });
这显然是脆弱的,如果版本不同步,就会崩溃

所以 是否有某种方法可以确保'module-a''module-b'都使用相同的构造函数实例?或者另一种不那么易碎的解决方案。

你所说的:

经过一些调试后,我发现(事后看来有点明显) “模块-a”中创建的错误不是已创建错误的实例 通过“模块b”

错误对象不能是另一个错误对象的实例。或者您是说当执行类似于CustomError的错误实例时,来自
模块a
模块b
的错误会返回不同的结果?请记住,
instanceof
测试对象的原型链中是否存在
构造函数。prototype
在对象的原型链中,当对
CustomError
进行测试时,来自这些模块的两个错误都应通过您发布的代码返回
true

您能说明您是如何在这些模块中创建这些错误的吗?

这是因为两个模块都有自己的JS文件副本 包含CustomError构造函数,两者都运行 独立地

我再一次被这句话弄糊涂了。你说两个模块都有自己的副本是什么意思?让我们举个小例子:

// 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')
首先,
errA
errB
都应该是CustomError的
实例

console.log(errA instanceof CustomError) // yields true
console.log(errB instanceof CustomError) // yields true
原型链中的
errA
errB
属性的
cunstructor
应具有指向相同函数对象的引用
CustomError

console.log(errA.constructor === errB.constructor) // yields true
让我们再介绍一个示例:

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)
})
结果:

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

最后一件事,在您的示例中,当您说
深度npm依赖时,您的意思是什么?这个
CustomError
是您的模块还是第三方库?无论是否是第三方模块,这都不会改变任何事情。

这实际上也是浏览器中的一个问题,当您有一个iframe时,它会获得自己的副本,例如数组构造函数(使
instanceof
无用)

自定义构造函数的解决方案是duck类型它。以下是一些有利弊的潜在解决方案

  • 检查构造函数名称。赞成:简单,支持良好。缺点:最好选择一个相当独特的名称,以避免误报,并忘记对其进行分类

  • 检查对象的属性(例如,同时具有'foo'和'bar','foo'是一个函数)。赞成者:大多是傻瓜式的。缺点:脆弱性:如果重构自定义错误类,此检查可能会随机中断,这相对比较昂贵

  • (推荐)添加属性/方法。许多库(例如,moment.js)就是这样处理这个问题的

  • 代码示例:

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

    这或多或少是矩检测给定对象是否为矩对象的准确方式。

    是的,很好,这基本上就是我现在所做的,似乎是合理的。谢谢所以我一直在想,另一个(相当好的)方法是重写
    Symbol.hasInstance
    ,但目前对此的支持有点缺乏。@phenomnomnominal是的。这是一个非常准确的用例。总有一天我们会到达那里……你缺少的是它们是发布的模块。因此,它不是
    const CustomError=require('./CustomError')
    而是
    const CustomError=require('custom-error')
    。这就是他们获取实际源代码的两个副本的方式,它们独立运行,创建两个不同的构造函数。