Я хочу добавить некоторые вещи в мой код JS, и я хочу, чтобы они были экземпляром Error, но я также хочу, чтобы они были чем-то другим.
В Python, как правило, подкласс Exception.
Что нужно делать в JS?
Я хочу добавить некоторые вещи в мой код JS, и я хочу, чтобы они были экземпляром Error, но я также хочу, чтобы они были чем-то другим.
В Python, как правило, подкласс Exception.
Что нужно делать в JS?
Ответы:
Единственное стандартное поле объекта 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 самостоятельно сделал Сравнение объектов ошибок .
function MyError(message) { this.message = message; this.stack = Error().stack; } MyError.prototype = Object.create(Error.prototype); MyError.prototype.name = "MyError";
MyError.prototype.constructor = MyError
тоже добавил .
this
, верно?
В ES6:
class MyError extends Error {
constructor(message) {
super(message);
this.name = 'MyError';
}
}
var supportsClasses = false; try {eval('class X{}'); supportsClasses = true;} catch (e) {}
this.name = this.constructor.name;
вместо этого.
Короче говоря:
Если вы используете ES6 без транспортеров :
class CustomError extends Error { /* ... */}
Если вы используете транспортер Babel :
Вариант 1: использовать 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
В самом деле, из официальной документации Вавилонского, вы не можете расширить любой встроенные классы JavaScript , такие как Date
, Array
, DOM
или Error
.
Проблема описана здесь:
А как насчет других ответов 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
class CustomError extends Error { /* ... */}
неправильно обрабатывает специфичные для поставщика аргументы ( lineNumber
и т. д.), «Расширение ошибки в Javascript с синтаксисом ES6» является специфическим для Babel, ваше решение ES5 использует const
и не обрабатывает пользовательские аргументы.
console.log(new CustomError('test') instanceof CustomError);// false
была верной на момент написания, но теперь была решена. Фактически, проблема, связанная с ответом , была решена, и мы можем проверить правильное поведение здесь , вставив код в REPL и посмотрев, как он правильно передается для создания экземпляра с правильной цепочкой прототипов.
Изменить: Пожалуйста, прочитайте комментарии. Оказывается, это хорошо работает только в 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
Error.call(this)
действительно ничего не делает, так как он возвращает ошибку, а не изменение this
.
UserError.prototype = Error.prototype
вводит в заблуждение. Это не делает наследование, это делает их одним и тем же классом .
Object.setPrototypeOf(this.constructor.prototype, Error.prototype)
что предпочтительнее this.constructor.prototype.__proto__ = 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';
});
this.name = name;
?
name
он уже установлен на прототип, он больше не нужен. Я удалил это. Спасибо!
В 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__
свойство устарело, что широко используется в других ответах.
setPrototypeOf()
? По крайней мере, согласно MDN, обычно не рекомендуется использовать его, если вы можете выполнить то же самое, просто установив .prototype
свойство в конструкторе (как вы делаете в else
блоке для браузеров, которые не имеют setPrototypeOf
).
setPrototypeOf
. Но если вам все еще это нужно (как требует ОП), вам следует использовать встроенную методологию. Как указывает MDN, это считается правильным способом установки прототипа объекта. Другими словами, MDN говорит, что не изменяйте прототип (поскольку это влияет на производительность и оптимизацию), но, если вам нужно, используйте setPrototypeOf
.
CustomError.prototype = Object.create(Error.prototype)
). Кроме того, Object.setPrototypeOf(CustomError, Error.prototype)
это установка прототипа самого конструктора, а не указание прототипа для новых экземпляров CustomError
. Во всяком случае, в 2016 году я думаю, что на самом деле есть лучший способ расширить ошибки, хотя я все еще выясняю, как использовать их вместе с Babel: github.com/loganfsmyth/babel-plugin-transform-builtin-extend/…
CustomError.prototype = Object.create(Error.prototype)
Также меняется прототип. Вы должны изменить его, поскольку в ES5 нет встроенной логики расширения / наследования. Я уверен, что плагин Babel, который вы упоминаете, делает подобные вещи.
Object.setPrototypeOf
здесь не имеет смысла, по крайней мере, не так, как вы его используете: gist.github.com/mbrowne/4af54767dcb3d529648f5a8aa11d6348 . Возможно, вы намеревались написать Object.setPrototypeOf(CustomError.prototype, Error.prototype)
- это имело бы немного больше смысла (хотя все равно не давало никакой выгоды по сравнению с простыми настройками CustomError.prototype
)
Для полноты картины - просто потому, что ни один из предыдущих ответов не упомянул этот метод - если вы работаете с 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
экземпляра). Для получения более подробной информации о том, как это работает, проверьте документацию здесь .
this.message = this.message;
это неправильно или есть еще сумасшедшие вещи, которые я не знаю о JS?
Ответ 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, чтобы найти реальное местоположение экземпляра.
Crescent Fresh's
был удален!
Как насчет этого решения?
Вместо того, чтобы выдавать свою собственную ошибку, используя:
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();
};
new MyErr (arg1, arg2, new Error())
и в конструкторе MyErr мы используем Object.assign
для присваивания свойств последнего аргументаthis
Как говорят некоторые люди, с 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;
}
}
Мое решение более простое, чем другие ответы, и не имеет недостатков.
Он сохраняет цепочку прототипов 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;
};
throw CustomError('err')
вместоthrow new CustomError('err')
В приведенном выше примере 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);
}
Я просто хочу добавить к тому, что уже заявили другие:
Чтобы убедиться, что пользовательский класс ошибок правильно отображается в трассировке стека, необходимо установить для свойства имени прототипа пользовательского класса ошибок значение свойства имени пользовательского класса ошибок. Это то, что я имею в виду:
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)
Мои 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__=
делает.
Поскольку исключения 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.
m = new InvalidInputError(); dontThrowMeYet(m);
m = new ...
тогда Promise.reject(m)
. В этом нет необходимости, но код легче читать.
Как указано в ответе Мохсена, в 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, лично я не уверен, что сложность такого подхода того стоит.
Способ сделать это правильно - вернуть результат применения из конструктора, а также установить прототип обычным сложным способом 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 () ??).
Фрагмент показывает все это.
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)
}
}
Я бы сделал шаг назад и подумал, почему ты хочешь это сделать? Я думаю, что дело в разных ошибках по-разному.
Например, в 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
}
error.stack
, стандартный инструмент не будет работать с ним и т. Д. И т. Д. Лучше было бы добавить свойства к экземпляру ошибки, например,var e = new Error(); e.type = "validation"; ...
Это основано на ответе Джорджа Бэйли , но расширяет и упрощает первоначальную идею. Он написан на CoffeeScript, но его легко конвертировать в JavaScript. Идея заключается в расширении пользовательской ошибки Bailey с помощью декоратора, который оборачивает ее, позволяя вам легко создавать новые пользовательские ошибки.
Примечание: это будет работать только в 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"
Для забавы вы можете теперь определить функцию, которая выдает a, 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, нам не нужно устанавливать callstack и сообщение
У Мохсена есть отличный ответ выше в 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");
this.name = 'MyError'
внешнюю часть функции и изменить ее наMyError.prototype.name = 'MyError'
.