对使用深度npm依赖项的构造函数创建的对象使用`instanceof`

phe*_*nal 15 javascript node.js npm

背景:

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

function CustomError () { /* ... */ }
CustomError.prototype = Object.create(Error.prototype);
CustomError.prototype.constructor = CustomError;
module.exports = CustomError;
Run Code Online (Sandbox Code Playgroud)

我有一些其他模块(我们称之为'module-a''module-b'),它们都依赖于错误处理模块.

我也有一些使用Bluebirds"过滤捕获"功能的代码:

doSomething
.catch(CustomError, () => { /* ... */ });
Run Code Online (Sandbox Code Playgroud)

问题:

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

我宁愿不必诉诸我目前的解决方案,基本上是:

CustomError.isCustomError = e => e.constructor.toString() === CustomError.toString();
Run Code Online (Sandbox Code Playgroud)

然后:

doSomething
.then(CustomError.isCustomError, () => { /* ... */ });
Run Code Online (Sandbox Code Playgroud)

这显然是脆弱的,如果版本不同步将会崩溃.

所以...

有没有办法确保'module-a''module-b'都使用相同的构造函数实例?或者另一个不太脆弱的解决方案.

Jar*_*ith 6

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

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

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

  2. 检查对象的属性(例如,同时具有 'foo' 和 'bar 并且 'foo' 是一个函数)。优点:主要是万无一失。缺点:脆弱:如果重构自定义错误类,此检查可能会随机中断,相对昂贵。

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

代码示例:

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

module.exports = {
  CustomError,
  isCustomError
};
Run Code Online (Sandbox Code Playgroud)

这或多或少正是 moment 检测给定对象是否为 moment 对象的方式。


Srl*_*rle -2

你是什​​么意思:

经过一些调试后,我发现(事后看来很明显)“module-a”中创建的错误不是“module-b”创建的错误的实例。

错误对象不能是另一个错误对象的实例。或者您是说执行类似操作时出现的错误module-amodule-b返回err instanceof CustomError不同的结果?请记住,测试对象原型链中instanceof是否存在,在再次测试时,这些模块中的两个错误都应通过您发布的代码返回。constructor.prototypetrueCustomError

您能展示一下您是如何在这些模块中创建这些错误的吗?

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

我再次对这种说法感到困惑。两个模块都有自己的副本是什么意思?让我们举个小例子:

// 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')
Run Code Online (Sandbox Code Playgroud)

首先errA和 都errB应该是instanceof CustomError

console.log(errA instanceof CustomError) // yields true
console.log(errB instanceof CustomError) // yields true
Run Code Online (Sandbox Code Playgroud)

cunstructorerrA的和的属性errB将在原型链中找到,应该具有指向相同函数对象的引用CustomError

console.log(errA.constructor === errB.constructor) // yields true
Run Code Online (Sandbox Code Playgroud)

我们还介绍一下过滤捕获示例:

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)
})
Run Code Online (Sandbox Code Playgroud)

结果:

instance of CustomError catched [Error]
instance of CustomError catched again [Error]
Run Code Online (Sandbox Code Playgroud)

最后一件事,在你的例子中,你说的是什么意思deep npm dependencies?这个CustomError东西是你的模块还是第三方库?无论是否是第 3 方模块,这一事实不应改变任何内容。

  • 您缺少的一点是它们是“已发布”模块。因此,它实际上是“const CustomError = require('./custom-error')”,而不是“const CustomError = require('./custom-error')”。这就是他们获取实际源代码的两个副本的方式,这些副本独立运行,创建两个不同的构造函数。 (2认同)