Как создать пользовательскую ошибку в JavaScript?
По какой-то причине похоже, что делегирование конструктора не работает в следующем фрагменте:
function NotImplementedError() {
Error.apply(this, arguments);
}
NotImplementedError.prototype = new Error();
var nie = new NotImplementedError("some message");
console.log("The message is: '"+nie.message+"'")
Запуск этого дает The message is: ''
. Любые идеи о том, почему, или если есть лучший способ создать новый подкласс Error
? Есть ли проблема с apply
ing для встроенного конструктора Error
, о котором я не знаю?
Ответы
Ответ 1
Обновите свой код, чтобы назначить ваш прототип Error.prototype, а instanceof и ваши утверждения работают.
function NotImplementedError(message) {
this.name = "NotImplementedError";
this.message = (message || "");
}
NotImplementedError.prototype = Error.prototype;
Однако я бы просто выбросил ваш собственный объект и просто проверил свойство name.
throw {name : "NotImplementedError", message : "too lazy to implement"};
Изменить на основе комментариев
После просмотра комментариев и попыток вспомнить, почему я бы назначил прототип Error.prototype
вместо new Error()
, как это сделал Николас Закас в своей статье , я создал jsFiddle с помощью кода ниже:
function NotImplementedError(message) {
this.name = "NotImplementedError";
this.message = (message || "");
}
NotImplementedError.prototype = Error.prototype;
function NotImplementedError2(message) {
this.message = (message || "");
}
NotImplementedError2.prototype = new Error();
try {
var e = new NotImplementedError("NotImplementedError message");
throw e;
} catch (ex1) {
console.log(ex1.stack);
console.log("ex1 instanceof NotImplementedError = " + (ex1 instanceof NotImplementedError));
console.log("ex1 instanceof Error = " + (ex1 instanceof Error));
console.log("ex1.name = " + ex1.name);
console.log("ex1.message = " + ex1.message);
}
try {
var e = new NotImplementedError2("NotImplementedError2 message");
throw e;
} catch (ex1) {
console.log(ex1.stack);
console.log("ex1 instanceof NotImplementedError2 = " + (ex1 instanceof NotImplementedError2));
console.log("ex1 instanceof Error = " + (ex1 instanceof Error));
console.log("ex1.name = " + ex1.name);
console.log("ex1.message = " + ex1.message);
}
Ответ 2
Все вышеперечисленные ответы ужасно ужасные - действительно. Даже тот, у которого есть 107 взлетов! Реальный ответ: ребята:
Наследование объекта Error - где это свойство сообщения?
TL; ДР:
а. Причина message
не установлена, так это то, что Error
- это функция, которая возвращает новый объект Error и никак не манипулирует this
.
В. Способ сделать это правильно - вернуть результат применения от конструктора, а также установить прототип в обычном сложном способе javascripty:
function MyError() {
var temp = Error.apply(this, arguments);
temp.name = this.name = 'MyError';
this.message = temp.message;
if(Object.defineProperty) {
// getter for more optimizy goodness
/*this.stack = */Object.defineProperty(this, 'stack', {
get: function() {
return temp.stack
},
configurable: true // so you can change it if you want
})
} else {
this.stack = temp.stack
}
}
//inherit prototype using ECMAScript 5 (IE 9+)
MyError.prototype = Object.create(Error.prototype, {
constructor: {
value: MyError,
writable: true,
configurable: true
}
});
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 ...>
//for EMCAScript 4 or ealier (IE 8 or ealier), inherit prototype this way instead of above code:
/*
var IntermediateInheritor = function() {};
IntermediateInheritor.prototype = Error.prototype;
MyError.prototype = new IntermediateInheritor();
*/
Ответ 3
Если кому-то интересно узнать, как создать пользовательскую ошибку и получить трассировку стека:
function CustomError(message) {
this.name = 'CustomError';
this.message = message || '';
var error = new Error(this.message);
error.name = this.name;
this.stack = error.stack;
}
CustomError.prototype = Object.create(Error.prototype);
try {
throw new CustomError('foobar');
}
catch (e) {
console.log('name:', e.name);
console.log('message:', e.message);
console.log('stack:', e.stack);
}
Ответ 4
В ES2015 вы можете использовать class
для этого:
class NotImplemented extends Error {
constructor(message = "", ...args) {
super(message, ...args);
this.message = message + " has not yet been implemented.";
}
}
Это не изменяет глобальный прототип Error
, позволяет настраивать message
, name
и другие атрибуты и правильно захватывать стек. Это также довольно читаемо.
Конечно, вам может понадобиться использовать такой инструмент, как babel
, если ваш код будет работать в старых браузерах.
Ответ 5
Этот раздел стандарта может объяснить, почему вызов Error.apply
не инициализирует объект:
15.11.1 Конструктор ошибок, вызываемый как функция
Когда Ошибка вызывается как функция, а не как конструктор, она создает и инициализирует новый объект Error. Таким образом, вызов функции Error (...) эквивалентно выражению создания объекта new Error (...) с те же аргументы.
В этом случае функция Error
, вероятно, определяет, что она не вызывается как конструктор, поэтому возвращает новый экземпляр Error, а не инициализирует объект this
.
Тестирование с помощью следующего кода, похоже, показывает, что это на самом деле происходит:
function NotImplementedError() {
var returned = Error.apply(this, arguments);
console.log("returned.message = '" + returned.message + "'");
console.log("this.message = '" + this.message + "'");
}
NotImplementedError.prototype = new Error();
var nie = new NotImplementedError("some message");
При этом запускается следующий вывод:
returned.message = 'some message'
this.message = ''
Ответ 6
У меня была аналогичная проблема. Моя ошибка должна быть instanceof
как Error
, так и NotImplemented
, а также необходимо создать согласованную обратную трассировку в консоли.
Мое решение:
var NotImplemented = (function() {
var NotImplemented, err;
NotImplemented = (function() {
function NotImplemented(message) {
var err;
err = new Error(message);
err.name = "NotImplemented";
this.message = err.message;
if (err.stack) this.stack = err.stack;
}
return NotImplemented;
})();
err = new Error();
err.name = "NotImplemented";
NotImplemented.prototype = err;
return NotImplemented;
}).call(this);
// TEST:
console.log("instanceof Error: " + (new NotImplemented() instanceof Error));
console.log("instanceof NotImplemented: " + (new NotImplemented() instanceofNotImplemented));
console.log("message: "+(new NotImplemented('I was too busy').message));
throw new NotImplemented("just didn't feel like it");
Результат работы с node.js:
instanceof Error: true
instanceof NotImplemented: true
message: I was too busy
/private/tmp/t.js:24
throw new NotImplemented("just didn't feel like it");
^
NotImplemented: just didn't feel like it
at Error.NotImplemented (/Users/colin/projects/gems/jax/t.js:6:13)
at Object.<anonymous> (/Users/colin/projects/gems/jax/t.js:24:7)
at Module._compile (module.js:449:26)
at Object.Module._extensions..js (module.js:467:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Module.runMain (module.js:487:10)
at process.startup.processNextTick.process._tickCallback (node.js:244:9)
Ошибка пропускает все 3 из моих критериев, и хотя свойство stack
нестандартно, оно поддерживается в большинстве новых браузеров, которое приемлемый в моем случае.
Ответ 7
function InvalidValueError(value, type) {
this.message = "Expected `" + type.name + "`: " + value;
var error = new Error(this.message);
this.stack = error.stack;
}
InvalidValueError.prototype = new Error();
InvalidValueError.prototype.name = InvalidValueError.name;
InvalidValueError.prototype.constructor = InvalidValueError;
Ответ 8
Соглашаясь с Joyent, вы не должны испортить свойство стека (что я вижу во многих ответах, приведенных здесь), поскольку это будет иметь негативное влияние на производительность. Вот что они говорят:
stack: вообще, не путайте с этим. Даже не увеличивайте его. V8 только вычисляет его, если кто-то действительно читает свойство, что значительно повышает производительность для ручных ошибок. Если вы читаете свойство только для его увеличения, вы в конечном итоге оплачиваете стоимость, даже если вашему абоненту не нужен стек.
Мне нравится и хотелось бы упомянуть о своей идее обернуть исходную ошибку, которая является хорошей заменой для передачи в стеке.
Итак, вот как я создаю пользовательскую ошибку, учитывая вышеупомянутое:
Версия es5:
function RError(options) {
options = options || {}; // eslint-disable-line no-param-reassign
this.name = options.name;
this.message = options.message;
this.cause = options.cause;
// capture stack (this property is supposed to be treated as private)
this._err = new Error();
// create an iterable chain
this.chain = this.cause ? [this].concat(this.cause.chain) : [this];
}
RError.prototype = Object.create(Error.prototype, {
constructor: {
value: RError,
writable: true,
configurable: true
}
});
Object.defineProperty(RError.prototype, 'stack', {
get: function stack() {
return this.name + ': ' + this.message + '\n' + this._err.stack.split('\n').slice(2).join('\n');
}
});
Object.defineProperty(RError.prototype, 'why', {
get: function why() {
var _why = this.name + ': ' + this.message;
for (var i = 1; i < this.chain.length; i++) {
var e = this.chain[i];
_why += ' <- ' + e.name + ': ' + e.message;
}
return _why;
}
});
// usage
function fail() {
throw new RError({
name: 'BAR',
message: 'I messed up.'
});
}
function failFurther() {
try {
fail();
} catch (err) {
throw new RError({
name: 'FOO',
message: 'Something went wrong.',
cause: err
});
}
}
try {
failFurther();
} catch (err) {
console.error(err.why);
console.error(err.stack);
console.error(err.cause.stack);
}
Ответ 9
Мне просто пришлось реализовать что-то подобное и обнаружил, что стек был потерян в моей собственной реализации ошибки. Мне нужно было создать фиктивную ошибку и извлечь из нее стек:
My.Error = function (message, innerException) {
var err = new Error();
this.stack = err.stack; // IMPORTANT!
this.name = "Error";
this.message = message;
this.innerException = innerException;
}
My.Error.prototype = new Error();
My.Error.prototype.constructor = My.Error;
My.Error.prototype.toString = function (includeStackTrace) {
var msg = this.message;
var e = this.innerException;
while (e) {
msg += " The details are:\n" + e.message;
e = e.innerException;
}
if (includeStackTrace) {
msg += "\n\nStack Trace:\n\n" + this.stack;
}
return msg;
}
Ответ 10
Я использовал шаблон конструктора для создания нового объекта ошибки. Я определил цепочку прототипов , например экземпляр Error
. См. Ссылку MDN конструктор ошибок.
Вы можете проверить этот фрагмент на этом gist.
ВЫПОЛНЕНИЕ
// Creates user-defined exceptions
var CustomError = (function() {
'use strict';
//constructor
function CustomError() {
//enforces 'new' instance
if (!(this instanceof CustomError)) {
return new CustomError(arguments);
}
var error,
//handles the arguments object when is passed by enforcing a 'new' instance
args = Array.apply(null, typeof arguments[0] === 'object' ? arguments[0] : arguments),
message = args.shift() || 'An exception has occurred';
//builds the message with multiple arguments
if (~message.indexOf('}')) {
args.forEach(function(arg, i) {
message = message.replace(RegExp('\\{' + i + '}', 'g'), arg);
});
}
//gets the exception stack
error = new Error(message);
//access to CustomError.prototype.name
error.name = this.name;
//set the properties of the instance
//in order to resemble an Error instance
Object.defineProperties(this, {
stack: {
enumerable: false,
get: function() { return error.stack; }
},
message: {
enumerable: false,
value: message
}
});
}
// Creates the prototype and prevents the direct reference to Error.prototype;
// Not used new Error() here because an exception would be raised here,
// but we need to raise the exception when CustomError instance is created.
CustomError.prototype = Object.create(Error.prototype, {
//fixes the link to the constructor (ES5)
constructor: setDescriptor(CustomError),
name: setDescriptor('JSU Error')
});
function setDescriptor(value) {
return {
configurable: false,
enumerable: false,
writable: false,
value: value
};
}
//returns the constructor
return CustomError;
}());
ИСПОЛЬЗОВАНИЕ
Конструктор CustomError может принимать множество аргументов для создания сообщения, например.
var err1 = new CustomError("The url of file is required"),
err2 = new CustomError("Invalid Date: {0}", +"date"),
err3 = new CustomError("The length must be greater than {0}", 4),
err4 = new CustomError("Properties .{0} and .{1} don't exist", "p1", "p2");
throw err4;
И вот как выглядит пользовательская ошибка:
![Целевая цепочка прототипов ошибок]()
Ответ 11
Конструктор должен быть как метод factory и возвращать то, что вы хотите. Если вам нужны дополнительные методы/свойства, вы можете добавить их в объект, прежде чем возвращать его.
function NotImplementedError(message) { return new Error("Not implemented", message); }
x = new NotImplementedError();
Хотя я не уверен, зачем вам это нужно. Почему бы просто не использовать new Error...
? Пользовательские исключения на самом деле не очень много добавляют в JavaScript (или, возможно, в любом нетипизированном языке).
Ответ 12
Это прекрасно реализовано в Cesium DeveloperError:
В нем упрощенная форма:
var NotImplementedError = function(message) {
this.name = 'NotImplementedError';
this.message = message;
this.stack = (new Error()).stack;
}
// Later on...
throw new NotImplementedError();
Ответ 13
За счет невозможности использования instanceof
, следующая сохраняет исходную трассировку стека и не использует нестандартные трюки.
// the function itself
var fixError = function(err, name) {
err.name = name;
return err;
}
// using the function
try {
throw fixError(new Error('custom error message'), 'CustomError');
} catch (e) {
if (e.name == 'CustomError')
console.log('Wee! Custom Error! Msg:', e.message);
else
throw e; // unhandled. let it propagate upwards the call stack
}
Ответ 14
Другая альтернатива, возможно, не работает во всех средах. Скорее всего, она работает в узлах 0.8
В этом подходе используется нестандартный способ модификации внутреннего протопроп.
function myError(msg){
var e = new Error(msg);
_this = this;
_this.__proto__.__proto__ = e;
}
Ответ 15
Если вы используете Node/Chrome. Следующий фрагмент получит расширение, соответствующее следующим требованиям.
-
err instanceof Error
-
err instanceof CustomErrorType
- console.log() возвращает
[CustomErrorType]
при создании с сообщением
- console.log() возвращает
[CustomErrorType: message]
при создании без сообщения
- throw/stack предоставляет информацию в момент создания ошибки.
- Оптимально работает в Node.JS и Chrome.
- Пройдет проверку экземпляров в браузерах Chrome, Safari, Firefox и IE 8+, но не будет иметь действительный стек вне Chrome/Safari. Я в порядке с этим, потому что я могу отлаживать хром, но код, который требует определенных типов ошибок, будет по-прежнему работать с перекрестным браузером. Если вам нужен Node, вы можете легко удалить операторы
if
, и вам будет удобно идти.
Отрывок
var CustomErrorType = function(message) {
if (Object.defineProperty) {
Object.defineProperty(this, "message", {
value : message || "",
enumerable : false
});
} else {
this.message = message;
}
if (Error.captureStackTrace) {
Error.captureStackTrace(this, CustomErrorType);
}
}
CustomErrorType.prototype = new Error();
CustomErrorType.prototype.name = "CustomErrorType";
Использование
var err = new CustomErrorType("foo");
Выход
var err = new CustomErrorType("foo");
console.log(err);
console.log(err.stack);
[CustomErrorType: foo]
CustomErrorType: foo
at Object.<anonymous> (/errorTest.js:27:12)
at Module._compile (module.js:456:26)
at Object.Module._extensions..js (module.js:474:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Function.Module.runMain (module.js:497:10)
at startup (node.js:119:16)
at node.js:906:3
/errorTest.js:30
throw err;
^
CustomErrorType: foo
at Object.<anonymous> (/errorTest.js:27:12)
at Module._compile (module.js:456:26)
at Object.Module._extensions..js (module.js:474:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Function.Module.runMain (module.js:497:10)
at startup (node.js:119:16)
at node.js:906:3
Ответ 16
Это моя реализация:
class HttpError extends Error {
constructor(message, code = null, status = null, stack = null, name = null) {
super();
this.message = message;
this.status = 500;
this.name = name || this.constructor.name;
this.code = code || 'E_${this.name.toUpperCase()}';
this.stack = stack || null;
}
static fromObject(error) {
if (error instanceof HttpError) {
return error;
}
else {
const { message, code, status, stack } = error;
return new ServerError(message, code, status, stack, error.constructor.name);
}
}
expose() {
if (this instanceof ClientError) {
return { ...this };
}
else {
return {
name: this.name,
code: this.code,
status: this.status,
}
}
}
}
class ServerError extends HttpError {}
class ClientError extends HttpError { }
class IncorrectCredentials extends ClientError {
constructor(...args) {
super(...args);
this.status = 400;
}
}
class ResourceNotFound extends ClientError {
constructor(...args) {
super(...args);
this.status = 404;
}
}
Пример использования # 1:
app.use((req, res, next) => {
try {
invalidFunction();
}
catch (err) {
const error = HttpError.fromObject(err);
return res.status(error.status).send(error.expose());
}
});
Пример использования # 2:
router.post('/api/auth', async (req, res) => {
try {
const isLogged = await User.logIn(req.body.username, req.body.password);
if (!isLogged) {
throw new IncorrectCredentials('Incorrect username or password');
}
else {
return res.status(200).send({
token,
});
}
}
catch (err) {
const error = HttpError.fromObject(err);
return res.status(error.status).send(error.expose());
}
});
Ответ 17
Мне нравится делать это так, чтобы сообщение было одинаковым в stacktrace или toString, и я могу передать только имя или имя и сообщение. Например, если вы используете HTTP-ошибки, ваши обработчики могут просто error.toString()
пользователю, и он будет элегантно обрабатывать ваши ошибки или любые другие.
class AppException extends Error {
constructor(code, message) {
const fullMsg = message ? '${code}: ${message}' : code;
super(fullMsg);
this.name = code;
this.message = fullMsg;
}
toString() {
return this.message;
}
}
// Just an error name
try {
throw new AppException('Forbidden');
} catch(e) {
console.error(e);
console.error(e.toString());
}
// A name and a message
try {
throw new AppException('Forbidden', 'You don\'t have access to this page');
} catch(e) {
console.error(e);
console.error(e.toString());
}
Ответ 18
Я попытался объяснить все возможные способы создания пользовательских ошибок в JavaScript и как их использовать здесь. fooobar.com/questions/26282/....
Кроме того, чтобы ответить на вашу делегацию, я написал эту статью, которая объясняет это четко. https://medium.com/@amarpreet.singh/javascript-and-inheritance-90672f53d53c
Надеюсь, это поможет.
Ответ 19
Попробуйте создать новый объект-прототип для каждого экземпляра определенного пользователем типа ошибки. Он позволяет проверкам instanceof
вести себя как обычный плюс тип, и сообщение правильно сообщается в Firefox и V8 (Chome, nodejs).
function NotImplementedError(message){
if(NotImplementedError.innercall===undefined){
NotImplementedError.innercall = true;
NotImplementedError.prototype = new Error(message);
NotImplementedError.prototype.name = "NotImplementedError";
NotImplementedError.prototype.constructor = NotImplementedError;
return new NotImplementedError(message);
}
delete NotImplementedError.innercall;
}
Обратите внимание, что дополнительная запись будет предшествовать правильному стеку.
Ответ 20
проще. Вы можете сделать свой объект наследуемым от объекта Error.
Пример:
function NotImplementError(message)
{
this.message = message;
Error.call();
Error.call(message);
}
то, что мы делаем, это использование функции call(), которая вызывает конструктор класса Error, по существу, та же самая, что и реализация наследования класса на других объектно-ориентированных языках.
Ответ 21
MDN имеет отличный пример:
try {
throw new Error('Whoops!');
} catch (e) {
console.log(e.name + ': ' + e.message);
}