Какой хороший способ расширить Error в JavaScript?
Я хочу добавить некоторые вещи в мой код JS, и я хочу, чтобы они были экземпляром Error, но я также хочу, чтобы они были чем-то другим.
В Python, как правило, подкласс Exception.
Что нужно делать в JS?
27 ответов
В ES6:
class MyError extends Error {
constructor(message) {
super(message);
this.name = 'MyError';
}
}
Единственное стандартное поле объекта Error имеет message
имущество. (См. Спецификацию языка MDN или EcmaScript, раздел 15.11). Все остальное зависит от платформы.
Среда Mosts устанавливает stack
собственность, но fileName
а также lineNumber
практически бесполезны для использования в наследстве.
Итак, минималистичный подход это:
function MyError(message) {
this.name = 'MyError';
this.message = message;
this.stack = (new Error()).stack;
}
MyError.prototype = new Error; // <-- remove this if you do not
// want MyError to be instanceof Error
Вы можете прослушать стек, снять с него ненужные элементы и извлечь информацию, такую как fileName и lineNumber, но для этого требуется информация о платформе, на которой в данный момент работает JavaScript. В большинстве случаев это не нужно - и вы можете сделать это в посмертном порядке, если вы действительно хотите.
Safari является заметным исключением. Здесь нет stack
собственность, но throw
наборы ключевых слов sourceURL
а также line
свойства объекта, который бросается. Эти вещи гарантированно будут правильными.
Тестовые примеры, которые я использовал, можно найти здесь: JavaScript самостоятельно сделал Сравнение объектов ошибок.
Короче:
Если вы используете ES6 без транспортеров:
class CustomError extends Error { /* ... */}
Если вы используете транспортер Babel:
Вариант 1: использовать https://github.com/loganfsmyth/babel-plugin-transform-builtin-extend
Вариант 2: сделай сам (по мотивам той же библиотеки)
function CustomError(...args) {
const instance = Reflect.construct(Error, args);
Reflect.setPrototypeOf(instance, Reflect.getPrototypeOf(this));
return instance;
}
CustomError.prototype = Object.create(Error.prototype, {
constructor: {
value: Error,
enumerable: false,
writable: true,
configurable: true
}
});
Reflect.setPrototypeOf(CustomError, Error);
Если вы используете чистый ES5:
function CustomError(message, fileName, lineNumber) { var instance = new Error(message, fileName, lineNumber); Object.setPrototypeOf(instance, Object.getPrototypeOf(this)); return instance; } CustomError.prototype = Object.create(Error.prototype, { constructor: { value: Error, enumerable: false, writable: true, configurable: true } }); if (Object.setPrototypeOf){ Object.setPrototypeOf(CustomError, Error); } else { CustomError.__proto__ = Error; }
Альтернатива: использовать Classtrophobic framework
Объяснение:
Почему расширение класса Error с помощью ES6 и Babel является проблемой?
Потому что экземпляр CustomError больше не распознается как таковой.
class CustomError extends Error {}
console.log(new CustomError('test') instanceof Error);// true
console.log(new CustomError('test') instanceof CustomError);// false
Фактически, из официальной документации Babel вы не можете расширять какие-либо встроенные классы JavaScript, такие как Date
, Array
, DOM
или же Error
,
Проблема описана здесь:
- Родной расширяет разрывы HTMLELement, Array и другие
- объект класса, который расширяется базовым типом, таким как массив, число, объект, строка или ошибка, не является экземпляром этого класса
А как насчет других ответов SO?
Все приведенные ответы исправляют instanceof
проблема, но вы теряете обычную ошибку console.log
:
console.log(new CustomError('test'));
// output:
// CustomError {name: "MyError", message: "test", stack: "Error↵ at CustomError (<anonymous>:4:19)↵ at <anonymous>:1:5"}
Принимая во внимание, что, используя метод, упомянутый выше, не только вы исправляете instanceof
проблема, но вы также сохраняете обычную ошибку console.log
:
console.log(new CustomError('test'));
// output:
// Error: test
// at CustomError (<anonymous>:2:32)
// at <anonymous>:1:5
Изменить: Пожалуйста, прочитайте комментарии. Оказывается, это хорошо работает только в V8 (Chrome / Node.JS). Моим намерением было создание кросс-браузерного решения, которое работало бы во всех браузерах и обеспечивало трассировку стека там, где есть поддержка.
Изменить: я сделал это вики сообщества, чтобы позволить больше редактирования.
Решение для V8 (Chrome / Node.JS), работает в Firefox и может быть изменено для корректной работы в IE. (см. конец поста)
function UserError(message) {
this.constructor.prototype.__proto__ = Error.prototype // Make this an instanceof Error.
Error.call(this) // Does not seem necessary. Perhaps remove this line?
Error.captureStackTrace(this, this.constructor) // Creates the this.stack getter
this.name = this.constructor.name; // Used to cause messages like "UserError: message" instead of the default "Error: message"
this.message = message; // Used to set the message
}
Оригинальный пост "Покажи мне код!"
Укороченная версия:
function UserError(message) {
this.constructor.prototype.__proto__ = Error.prototype
Error.captureStackTrace(this, this.constructor)
this.name = this.constructor.name
this.message = message
}
я держу this.constructor.prototype.__proto__ = Error.prototype
внутри функции, чтобы держать весь код вместе. Но вы также можете заменить this.constructor
с UserError
и это позволяет вам переместить код за пределы функции, поэтому он вызывается только один раз.
Если вы идете по этому маршруту, убедитесь, что вы позвоните по этой линии до того, как в первый раз UserError
,
Это предостережение не применяет функцию, потому что функции создаются первыми, независимо от порядка. Таким образом, вы можете без проблем переместить функцию в конец файла.
Совместимость браузера
Работает в Firefox и Chrome (и Node.JS) и выполняет все обещания.
Internet Explorer не работает в следующем
Ошибок нету
err.stack
для начала, так что "это не моя вина".Error.captureStackTrace(this, this.constructor)
не существует, поэтому вам нужно сделать что-то еще, какif(Error.captureStackTrace) // AKA if not IE Error.captureStackTrace(this, this.constructor)
toString
перестает существовать, когда вы подклассError
, Так что вам тоже нужно добавить.else this.toString = function () { return this.name + ': ' + this.message }
IE не будет рассматривать
UserError
бытьinstanceof Error
если вы не выполните следующее некоторое время, прежде чемthrow UserError
UserError.prototype = Error.prototype
Чтобы избежать шаблонов для каждого типа ошибок, я объединил мудрость некоторых решений в createErrorType
функция:
function createErrorType(name, init) {
function E(message) {
if (!Error.captureStackTrace)
this.stack = (new Error()).stack;
else
Error.captureStackTrace(this, this.constructor);
this.message = message;
init && init.apply(this, arguments);
}
E.prototype = new Error();
E.prototype.name = name;
E.prototype.constructor = E;
return E;
}
Затем вы можете легко определить новые типы ошибок следующим образом:
var NameError = createErrorType('NameError', function (name, invalidChar) {
this.message = 'The name ' + name + ' may not contain ' + invalidChar;
});
var UnboundError = createErrorType('UnboundError', function (variableName) {
this.message = 'Variable ' + variableName + ' is not bound';
});
В 2018 году я думаю, что это лучший способ; который поддерживает IE9+ и современные браузеры.
ОБНОВЛЕНИЕ: см. Этот тест и репо для сравнения на разных реализациях.
function CustomError(message) {
Object.defineProperty(this, 'name', {
enumerable: false,
writable: false,
value: 'CustomError'
});
Object.defineProperty(this, 'message', {
enumerable: false,
writable: true,
value: message
});
if (Error.hasOwnProperty('captureStackTrace')) { // V8
Error.captureStackTrace(this, CustomError);
} else {
Object.defineProperty(this, 'stack', {
enumerable: false,
writable: false,
value: (new Error(message)).stack
});
}
}
if (typeof Object.setPrototypeOf === 'function') {
Object.setPrototypeOf(CustomError.prototype, Error.prototype);
} else {
CustomError.prototype = Object.create(Error.prototype, {
constructor: { value: CustomError }
});
}
Также будьте осторожны, что __proto__
собственность устарела, что широко используется в других ответах.
Для полноты картины - просто потому, что ни один из предыдущих ответов не упомянул этот метод - если вы работаете с Node.js и вам не нужно заботиться о совместимости браузера, желаемый эффект довольно легко достичь с помощью встроенного inherits
из util
модуль ( официальные документы здесь).
Например, предположим, что вы хотите создать пользовательский класс ошибок, который принимает код ошибки в качестве первого аргумента и сообщение об ошибке в качестве второго аргумента:
файл custom-error.js:
'use strict';
var util = require('util');
function CustomError(code, message) {
Error.captureStackTrace(this, CustomError);
this.name = CustomError.name;
this.code = code;
this.message = message;
}
util.inherits(CustomError, Error);
module.exports = CustomError;
Теперь вы можете создать экземпляр и передать / бросить CustomError
:
var CustomError = require('./path/to/custom-error');
// pass as the first argument to your callback
callback(new CustomError(404, 'Not found!'));
// or, if you are working with try/catch, throw it
throw new CustomError(500, 'Server Error!');
Обратите внимание, что при использовании этого фрагмента трассировка стека будет иметь правильное имя файла и строку, а экземпляр ошибки будет иметь правильное имя!
Это происходит из-за использования captureStackTrace
метод, который создает stack
свойство на целевом объекте (в этом случае CustomError
будучи экземпляром). Для получения более подробной информации о том, как это работает, проверьте документацию здесь.
Как говорили некоторые люди, с ES6 это довольно просто:
class CustomError extends Error { }
Поэтому я попробовал это в своем приложении (Angular, Typescript), и оно просто не сработало. Через некоторое время я обнаружил, что проблема исходит от Typescript:O
См. https://github.com/Microsoft/TypeScript/issues/13965
Это очень беспокоит, потому что если вы делаете:
class CustomError extends Error {}
try {
throw new CustomError()
} catch(e) {
if (e instanceof CustomError) {
console.log('Custom error');
} else {
console.log('Basic error');
}
}
В узле или непосредственно в вашем браузере будет отображаться: Custom error
Попробуйте запустить это с Typescript в вашем проекте на площадке Typescript, он будет отображаться Basic error
...
Решение состоит в том, чтобы сделать следующее:
class CustomError extends Error {
// we have to do the following because of: https://github.com/Microsoft/TypeScript/issues/13965
// otherwise we cannot use instanceof later to catch a given type
public __proto__: Error;
constructor(message?: string) {
const trueProto = new.target.prototype;
super(message);
this.__proto__ = trueProto;
}
}
Ответ Crescent Fresh с высоким рейтингом вводит в заблуждение. Хотя его предупреждения недействительны, есть и другие ограничения, которые он не рассматривает.
Во-первых, рассуждения в параграфе "Предостережения" Полумесяца не имеют смысла. Объяснение подразумевает, что кодирование "связки if (error instanceof MyError) else..." является каким-то обременительным или многословным по сравнению с несколькими операторами catch. Несколько операторов instanceof в одном блоке catch так же кратки, как и несколько операторов catch - чистый и лаконичный код без каких-либо хитростей. Это отличный способ эмулировать отличную обработку ошибок, характерную для бросаемых подтипов Java.
WRT "появляется сообщение, что свойство подкласса не устанавливается", это не тот случай, если вы используете правильно сконструированный подкласс Error. Чтобы создать собственный подкласс ErrorX Error, просто скопируйте блок кода, начинающийся с "var MyError =", заменив одно слово "MyError" на "ErrorX". (Если вы хотите добавить пользовательские методы в свой подкласс, следуйте примеру текста).
Реальное и существенное ограничение подклассов ошибок JavaScript заключается в том, что для реализаций JavaScript или отладчиков, которые отслеживают и сообщают о трассировке стека и расположении экземпляров, например FireFox, местоположение в вашей собственной реализации подкласса ошибок будет записываться как точка создания экземпляра. класс, тогда как если бы вы использовали прямую ошибку, это будет место, где вы запустили "новая ошибка (...)"). Пользователи IE, вероятно, никогда этого не заметят, но пользователи Fire Bug в FF увидят бесполезные значения имен файлов и номеров строк, сообщаемые вместе с этими ошибками, и им придется детализировать трассировку стека до элемента #1, чтобы найти реальное местоположение экземпляра.
Как насчет этого решения?
Вместо того, чтобы выдавать свою ошибку, используя:
throw new MyError("Oops!");
Вы бы обернули объект Error (вроде декоратора):
throw new MyError(Error("Oops!"));
Это гарантирует, что все атрибуты являются правильными, например, стек, имя_файла lineNumber и т. Д.
Все, что вам нужно сделать, это либо скопировать атрибуты, либо определить для них геттеры. Вот пример использования геттеров (IE9):
function MyError(wrapped)
{
this.wrapped = wrapped;
this.wrapped.name = 'MyError';
}
function wrap(attr)
{
Object.defineProperty(MyError.prototype, attr, {
get: function()
{
return this.wrapped[attr];
}
});
}
MyError.prototype = Object.create(Error.prototype);
MyError.prototype.constructor = MyError;
wrap('name');
wrap('message');
wrap('stack');
wrap('fileName');
wrap('lineNumber');
wrap('columnNumber');
MyError.prototype.toString = function()
{
return this.wrapped.toString();
};
Обновление 2021
В дополнение к стандартным
message
свойство, JavaScript теперь поддерживает добавление определенных
cause
ошибки как необязательный параметр для
Error
конструктор:
const error1 = new Error('Error one');
const error2 = new Error('Error two', { cause: error1 });
// error2.cause === error1
- Доступно в Node v16.9.0.
- Доступно в Chrome, Firefox и Safari ( версии см. В совместимости браузеров )
Мое решение более простое, чем другие ответы, и не имеет недостатков.
Он сохраняет цепочку прототипов Error и все свойства Error, не требуя специальных знаний о них. Он был протестирован в Chrome, Firefox, Node и IE11.
Единственным ограничением является дополнительная запись в верхней части стека вызовов. Но это легко игнорируется.
Вот пример с двумя пользовательскими параметрами:
function CustomError(message, param1, param2) {
var err = new Error(message);
Object.setPrototypeOf(err, CustomError.prototype);
err.param1 = param1;
err.param2 = param2;
return err;
}
CustomError.prototype = Object.create(
Error.prototype,
{name: {value: 'CustomError', enumerable: false}}
);
Пример использования:
try {
throw new CustomError('Something Unexpected Happened!', 1234, 'neat');
} catch (ex) {
console.log(ex.name); //CustomError
console.log(ex.message); //Something Unexpected Happened!
console.log(ex.param1); //1234
console.log(ex.param2); //neat
console.log(ex.stack); //stacktrace
console.log(ex instanceof Error); //true
console.log(ex instanceof CustomError); //true
}
Для сред, которым требуется полифайл setPrototypeOf:
Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
obj.__proto__ = proto;
return obj;
};
Мне не нравились все остальные ответы, слишком длинные, слишком сложные или неправильно отслеживали стек. Вот мой подход: если вам нужно больше настраиваемых свойств, передайте их конструктору и задайте им имя.
class CustomError extends Error {
constructor (message) {
super(message)
// needed for CustomError instanceof Error => true
Object.setPrototypeOf(this, new.target.prototype);
// Set the name
this.name = this.constructor.name
// Maintains proper stack trace for where our error was thrown (only available on V8)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor)
}
}
}
// create own CustomError sub classes
class SubCustomError extends CustomError{}
// Tests
console.log(new SubCustomError instanceof CustomError) // true
console.log(new SubCustomError instanceof CustomError) // true
console.log(new CustomError instanceof Error) // true
console.log(new SubCustomError instanceof Error) // true
throw new SubCustomError ('test error')
В приведенном выше примере Error.apply
(также Error.call
) ничего не делает для меня (Firefox 3.6/Chrome 5). Обходной путь, который я использую:
function MyError(message, fileName, lineNumber) {
var err = new Error();
if (err.stack) {
// remove one stack level:
if (typeof(Components) != 'undefined') {
// Mozilla:
this.stack = err.stack.substring(err.stack.indexOf('\n')+1);
}
else if (typeof(chrome) != 'undefined' || typeof(process) != 'undefined') {
// Google Chrome/Node.js:
this.stack = err.stack.replace(/\n[^\n]*/,'');
}
else {
this.stack = err.stack;
}
}
this.message = message === undefined ? err.message : message;
this.fileName = fileName === undefined ? err.fileName : fileName;
this.lineNumber = lineNumber === undefined ? err.lineNumber : lineNumber;
}
MyError.prototype = new Error();
MyError.prototype.constructor = MyError;
MyError.prototype.name = 'MyError';
В Node, как говорили другие, все просто:
class DumbError extends Error {
constructor(foo = 'bar', ...params) {
super(...params);
if (Error.captureStackTrace) {
Error.captureStackTrace(this, DumbError);
}
this.name = 'DumbError';
this.foo = foo;
this.date = new Date();
}
}
try {
let x = 3;
if (x < 10) {
throw new DumbError();
}
} catch (error) {
console.log(error);
}
Это не так уж сложно, но я лично считаю, что это самый простой способ легко исправить ошибку.
export default class ExtendableError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
}
}
Создайте служебный класс, например, так называемый
ExtendableError
. Цель этого служебного класса - быть похожей на обычный
Error
класс, но измените
name
свойство к имени класса по умолчанию, поэтому очень легко расширить ошибку.
Теперь, если вы хотите расширить ошибку, это займет всего одну строку.
class MyError extends ExtendableError {}
Мои 2 цента:
Почему другой ответ?
а) потому что доступ к Error.stack
свойство (как в некоторых ответах) имеет большой штраф производительности.
б) Потому что это только одна строка.
c) Потому что решение на https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error, похоже, не сохраняет информацию стека.
//MyError class constructor
function MyError(msg){
this.__proto__.__proto__ = Error.apply(null, arguments);
};
пример использования
http://jsfiddle.net/luciotato/xXyeB/
Что оно делает?
this.__proto__.__proto__
является MyError.prototype.__proto__
так что это настройка __proto__
ДЛЯ ВСЕХ МОМЕНТОВ MyError до определенной вновь созданной ошибки. Он сохраняет свойства и методы класса MyError, а также помещает новые свойства Error (включая.stack) в __proto__
цепь.
Очевидная проблема:
Вы не можете иметь более одного экземпляра MyError с полезной информацией стека.
Не используйте это решение, если вы не совсем понимаете, что this.__proto__.__proto__=
делает.
Я просто хочу добавить к тому, что уже заявили другие:
Чтобы убедиться, что пользовательский класс ошибок правильно отображается в трассировке стека, необходимо установить для свойства имени прототипа пользовательского класса ошибок значение свойства имени пользовательского класса ошибок. Это то, что я имею в виду:
CustomError.prototype = Error.prototype;
CustomError.prototype.name = 'CustomError';
Таким образом, полный пример будет:
var CustomError = function(message) {
var err = new Error(message);
err.name = 'CustomError';
this.name = err.name;
this.message = err.message;
//check if there is a stack property supported in browser
if (err.stack) {
this.stack = err.stack;
}
//we should define how our toString function works as this will be used internally
//by the browser's stack trace generation function
this.toString = function() {
return this.name + ': ' + this.message;
};
};
CustomError.prototype = new Error();
CustomError.prototype.name = 'CustomError';
Когда все сказано и сделано, вы бросаете новое исключение, и оно выглядит так (я лениво пробовал это в инструментах chrome dev):
CustomError: Stuff Happened. GASP!
at Error.CustomError (<anonymous>:3:19)
at <anonymous>:2:7
at Object.InjectedScript._evaluateOn (<anonymous>:603:39)
at Object.InjectedScript._evaluateAndWrap (<anonymous>:562:52)
at Object.InjectedScript.evaluate (<anonymous>:481:21)
Как указано в ответе Мохсена, в ES6 можно расширять ошибки, используя классы. Это намного проще, и их поведение больше соответствует собственным ошибкам... но, к сожалению, не так просто использовать это в браузере, если вам нужно поддерживать браузеры до ES6. Ниже приведены некоторые заметки о том, как это можно реализовать, но пока я предлагаю относительно простой подход, который включает в себя некоторые из лучших предложений из других ответов:
function CustomError(message) {
//This is for future compatibility with the ES6 version, which
//would display a similar message if invoked without the
//`new` operator.
if (!(this instanceof CustomError)) {
throw new TypeError("Constructor 'CustomError' cannot be invoked without 'new'");
}
this.message = message;
//Stack trace in V8
if (Error.captureStackTrace) {
Error.captureStackTrace(this, CustomError);
}
else this.stack = (new Error).stack;
}
CustomError.prototype = Object.create(Error.prototype);
CustomError.prototype.name = 'CustomError';
В ES6 это так просто, как:
class CustomError extends Error {}
... и вы можете обнаружить поддержку классов ES6 с try {eval('class X{}')
, но вы получите синтаксическую ошибку, если попытаетесь включить версию ES6 в скрипт, загруженный более старыми браузерами. Таким образом, единственным способом поддержки всех браузеров будет динамическая загрузка отдельного скрипта (например, через AJAX или eval()
) для браузеров, которые поддерживают ES6. Еще одним осложнением является то, что eval()
не поддерживается во всех средах (из-за политик безопасности содержимого), что может или не может быть соображением для вашего проекта.
Итак, пока первый подход выше или просто с помощью Error
напрямую, без попыток расширения, это, кажется, лучшее, что практически можно сделать для кода, который должен поддерживать браузеры не-ES6.
Есть еще один подход, который некоторые люди могут захотеть рассмотреть, который заключается в использовании Object.setPrototypeOf()
где возможно создать объект ошибки, который является экземпляром вашего пользовательского типа ошибки, но который выглядит и ведет себя больше как собственная ошибка в консоли (спасибо ответу Бена за рекомендацию). Вот мой взгляд на этот подход: https://gist.github.com/mbrowne/fe45db61cea7858d11be933a998926a8. Но учитывая, что однажды мы сможем просто использовать ES6, лично я не уверен, что сложность такого подхода того стоит.
У Мохсена есть отличный ответ выше в ES6, который задает имя, но если вы используете TypeScript или живете в будущем, где, надеюсь, это предложение для публичных и частных полей класса перешло за этап 3 как предложение и сделало его в этап 4 как часть ECMAScript/JavaScript, тогда вы, возможно, захотите узнать, что это будет немного короче. На этапе 3 браузеры начинают реализовывать функции, поэтому, если ваш браузер поддерживает это, приведенный ниже код может работать. (Протестировано в новом браузере Edge v81, похоже, работает нормально). Имейте в виду, что на данный момент это нестабильная функция, и ее следует использовать с осторожностью, и вы всегда должны проверять поддержку браузера для нестабильных функций. Этот пост предназначен в основном для тех будущих жителей, когда браузеры могут это поддерживать. Чтобы проверить поддержку, проверьте MDN и могу ли я использовать. В настоящее время он получил 66% -ную поддержку на рынке браузеров, которая становится там, но не настолько хороша, поэтому, если вы действительно хотите использовать его сейчас и не хотите ждать, используйте транспилятор, например Babel, или что-то вроде TypeScript.
class EOFError extends Error {
name="EOFError"
}
throw new EOFError("Oops errored");
Сравните это с безымянной ошибкой, которая при выбросе не регистрирует свое имя.
class NamelessEOFError extends Error {}
throw new NamelessEOFError("Oops errored");
Поскольку исключения JavaScript трудно подклассифицировать, я не подклассифицирую. Я просто создаю новый класс исключения и использую внутри него ошибку. Я изменяю свойство Error.name, чтобы оно выглядело как мое пользовательское исключение на консоли:
var InvalidInputError = function(message) {
var error = new Error(message);
error.name = 'InvalidInputError';
return error;
};
Вышеупомянутое новое исключение может быть сгенерировано так же, как обычная ошибка, и оно будет работать, как ожидается, например:
throw new InvalidInputError("Input must be a string");
// Output: Uncaught InvalidInputError: Input must be a string
Предостережение: трассировка стека не идеальна, так как она приведет вас к тому месту, где создается новая ошибка, а не к тому месту, куда вы ее бросаете. Это не имеет большого значения для Chrome, потому что он обеспечивает полную трассировку стека прямо в консоли. Но это более проблематично, например, в Firefox.
Фрагмент показывает все это.
function add(x, y) {
if (x && y) {
return x + y;
} else {
/**
*
* the error thrown will be instanceof Error class and InvalidArgsError also
*/
throw new InvalidArgsError();
// throw new Invalid_Args_Error();
}
}
// Declare custom error using using Class
class Invalid_Args_Error extends Error {
constructor() {
super("Invalid arguments");
Error.captureStackTrace(this);
}
}
// Declare custom error using Function
function InvalidArgsError(message) {
this.message = `Invalid arguments`;
Error.captureStackTrace(this);
}
// does the same magic as extends keyword
Object.setPrototypeOf(InvalidArgsError.prototype, Error.prototype);
try{
add(2)
}catch(e){
// true
if(e instanceof Error){
console.log(e)
}
// true
if(e instanceof InvalidArgsError){
console.log(e)
}
}
My proposed solution is to use the property of error to distinguish between error types instead of
instancof
This doesn't exactly answer the question, but I think makes for a reasonable solution, for some scenarios anyway.
The benefit I've seen of being able to have an
instanceof CustomError
is that you can do custom handling in your promise catch handler.
For example:
class CustomError extends Error {/** ... **/}
axios
.post(url, payload)
.then(data => {
if (!data.loggedIn) throw CustomError("not logged in");
return data;
})
.catch(error => {
if (error instanceof CustomError) {/** custom handling of error*//}
throw error
})
If that's what you're trying to accomplish, you be be well suited by the
.name
parameter as-well though:
export const ERROR_NOT_LOGGED_IN = "ERROR_NOT_LOGGED_IN";
axios
.post(url, payload)
.then(data => {
if (!data.loggedIn) throw Error("not logged in").name=ERROR_NOT_LOGGED_IN ;
return data;
})
.catch(error => {
if (error.name === ERROR_NOT_LOGGED_IN) {/** custom handling of error*//}
throw error
})
Способ сделать это правильно - вернуть результат применения из конструктора, а также установить прототип обычным сложным способом javascripty:
function MyError() {
var tmp = Error.apply(this, arguments);
tmp.name = this.name = 'MyError'
this.stack = tmp.stack
this.message = tmp.message
return this
}
var IntermediateInheritor = function() {}
IntermediateInheritor.prototype = Error.prototype;
MyError.prototype = new IntermediateInheritor()
var myError = new MyError("message");
console.log("The message is: '"+myError.message+"'") // The message is: 'message'
console.log(myError instanceof Error) // true
console.log(myError instanceof MyError) // true
console.log(myError.toString()) // MyError: message
console.log(myError.stack) // MyError: message \n
// <stack trace ...>
Единственные проблемы с этим способом сделать это в данный момент (я повторил это немного), что
- свойства, кроме
stack
а такжеmessage
не включены вMyError
а также - У трассировки стека есть дополнительная строка, которая на самом деле не нужна.
Первая проблема может быть решена путем итерации всех неперечислимых свойств ошибки с помощью хитрости в этом ответе: возможно ли получить неперечислимые имена унаследованных свойств объекта?, но это не поддерживается то есть<9. Вторую проблему можно решить, вырвав эту строку в трассировке стека, но я не уверен, как это безопасно сделать (возможно, просто удалив вторую строку из e.stack.toString()??).
Я бы сделал шаг назад и подумал, почему ты хочешь это сделать? Я думаю, что дело в том, чтобы иметь дело с разными ошибками по-разному.
Например, в Python вы можете ограничить оператор catch только catch MyValidationError
и, возможно, вы хотите иметь возможность сделать что-то подобное в JavaScript.
catch (MyValidationError e) {
....
}
Вы не можете сделать это в JavaScript. Там будет только один блок поймать. Вы должны использовать оператор if для определения ошибки.
catch(e) {
if(isMyValidationError(e)) {
...
} else {
// maybe rethrow?
throw e;
}
}
Я думаю, что вместо этого бросил бы необработанный объект с типом, сообщением и любыми другими свойствами, которые вы считаете подходящими.
throw { type: "validation", message: "Invalid timestamp" }
И когда вы ловите ошибку:
catch(e) {
if(e.type === "validation") {
// handle error
}
// re-throw, or whatever else
}
Декоратор пользовательских ошибок
Это основано на ответе Джорджа Бэйли, но расширяет и упрощает первоначальную идею. Он написан на CoffeeScript, но его легко конвертировать в JavaScript. Идея заключается в расширении пользовательской ошибки Бэйли с помощью декоратора, который оборачивает ее, позволяя легко создавать новые пользовательские ошибки.
Примечание: это будет работать только в V8. Там нет поддержки для Error.captureStackTrace
в других средах.
определять
Декоратор принимает имя для типа ошибки и возвращает функцию, которая принимает сообщение об ошибке и включает имя ошибки.
CoreError = (@message) ->
@constructor.prototype.__proto__ = Error.prototype
Error.captureStackTrace @, @constructor
@name = @constructor.name
BaseError = (type) ->
(message) -> new CoreError "#{ type }Error: #{ message }"
использование
Теперь просто создавать новые типы ошибок.
StorageError = BaseError "Storage"
SignatureError = BaseError "Signature"
Для забавы вы можете теперь определить функцию, которая выдает SignatureError
если он вызывается со слишком многими аргументами.
f = -> throw SignatureError "too many args" if arguments.length
Это было проверено довольно хорошо и, кажется, отлично работает на V8, поддерживая отслеживание, положение и т. Д.
Примечание: использование new
не является обязательным при построении пользовательской ошибки.
Если вас не волнует производительность при ошибках, это самое маленькое, что вы можете сделать
Object.setPrototypeOf(MyError.prototype, Error.prototype)
function MyError(message) {
const error = new Error(message)
Object.setPrototypeOf(error, MyError.prototype);
return error
}
вы можете использовать его без нового MyError(сообщение)
Изменяя прототип после вызова конструктора Error, нам не нужно устанавливать стек вызовов и сообщение.