Использование `instanceof` на объектах, созданных с помощью конструкторов из глубоких зависимостей npm
История:
У меня есть модуль npm, в котором я использую общий код обработки ошибок, включая пользовательскую ошибку:
function CustomError () { /* ... */ }
CustomError.prototype = Object.create(Error.prototype);
CustomError.prototype.constructor = CustomError;
module.exports = CustomError;
У меня есть несколько других модулей (пусть их называют 'module-a' и 'module-b'), которые зависят от модуля обработки ошибок.
У меня также есть код с функциями Bluebirds "отфильтрованный улов":
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' используют один и тот же экземпляр конструктора? Или другое, менее хрупкое решение.
Ответы
Ответ 1
Это тоже проблема в браузерах, когда у вас есть iframe, он получает свою собственную копию, например, конструктор Array (делая instanceof
бесполезным).
Решение для пользовательского конструктора состоит в том, чтобы утинить его. Вот некоторые потенциальные решения с плюсами и минусами.
-
Проверьте имя конструктора. Pro: простой, хорошо поддержанный. Con: лучше выбрать довольно уникальное имя, чтобы избежать ложных срабатываний и забыть о его подклассификации.
-
Проверьте свойства объекта (например, имеет функцию "foo" и "bar" и "foo" - функция). Pro: в основном дурака. Минусы: хрупкие: эта проверка может быть случайным образом нарушена, если вы реорганизуете свой собственный класс ошибок, относительно дорого.
-
(рекомендуется) Добавьте свойство/метод. Вот как справляется с этим множество библиотек (например, moment.js).
Пример кода:
CustomError.prototype._isCustomError = true;
var isCustomError = function isCustomError(obj) {
return obj instanceof CustomError || Boolean(obj._isCustomError);
};
module.exports = {
CustomError,
isCustomError
};
Это более или менее точно, как момент определяет, является ли данный объект объектом момента.
Ответ 2
Что вы подразумеваете под:
После некоторой отладки, которую я обнаружил (несколько явно задним числом) что ошибки, созданные в модуле-a, не являются экземплярами созданных ошибок по модулю -b.
Объект Error не может быть экземпляром другого объекта ошибки. Или вы говорите, что ошибки от module-a
или module-b
при выполнении чего-то вроде err instanceof CustomError
возвращают разные результаты? Имея в виду, что instanceof
проверяет наличие constructor.prototype
в цепочке прототипов объекта, обе ошибки из этих модулей должны возвращать true
по коду, который вы отправили, при повторном тестировании CustomError
.
Можете ли вы показать, как вы создаете эти ошибки в этих модулях?
Это связано с тем, что оба модуля имеют собственную копию 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
должны быть instanceof CustomError
:
console.log(errA instanceof CustomError) // yields true
console.log(errB instanceof CustomError) // yields true
cunstructor
свойство errA
и errB
, которое будет найдено в цепочке прототипов, должно иметь ссылку, указывающую на тот же объект функции 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]
И последнее, что вы имеете в виду в своем примере, когда вы говорите deep npm dependencies
? Это CustomError
вещь - ваш модуль или сторонняя библиотека? Тот факт, что является сторонним модулем или нет, это ничего не меняет.