Наследование от объекта Error - где находится свойство message?
Я заметил странное поведение при определении пользовательских объектов ошибок в Javascript:
function MyError(msg) {
Error.call(this, msg);
this.name = "MyError";
}
MyError.prototype.__proto__ = Error.prototype;
var error = new Error("message");
error.message; // "message"
var myError = new MyError("message");
myError instanceof Error; // true
myError.message; // "" !
Почему new Error("message")
установить message
собственность, в то время как Error.call(this, msg);
не? Конечно, я могу просто определить this.message = msg
в MyError
конструктор, но я не совсем понимаю, почему он еще не установлен в первую очередь.
7 ответов
А. Как, сказал Рейнос, причина message
не устанавливается, что Error
это функция, которая возвращает новый объект Error и не манипулирует this
в любом случае.
B. Способ сделать это правильно - установить результат применения из конструктора на this
, а также установка прототипа обычным сложным javascripty способом:
function MyError() {
var tmp = Error.apply(this, arguments)
tmp.name = this.name = 'MyError'
this.message = tmp.message
// instead of this.stack = ..., a getter for more optimizy goodness
Object.defineProperty(this, 'stack', {
get: function () {
return tmp.stack
}
})
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()??).
Обновить
Я создал библиотеку наследования, которая делает это ^ https://github.com/fresheneesz/proto
function MyError(msg) {
var err = Error.call(this, msg);
err.name = "MyError";
return err;
}
Error
не манипулирует this
, он создает новый объект ошибки, который возвращается. Вот почему Error("foo")
работает хорошо без new
ключевое слово.
Обратите внимание, что это зависит от реализации, v8 (chrome & node.js) ведет себя так.
Также MyError.prototype.__proto__ = Error.prototype;
плохая практика использование
MyError.prototype = Object.create(Error.prototype, {
constructor: { value: MyError }
});
В Node.js вы можете создать пользовательскую ошибку, например:
var util = require('util');
function MyError(message) {
this.message = message;
Error.captureStackTrace(this, MyError);
}
util.inherits(MyError, Error);
MyError.prototype.name = 'MyError';
Смотрите captureStackTrace в документации по узлам
Что не так с ES6?
class MyError extends Error {
constructor(message) {
super(message);
// Maintains proper stack trace (only on V8)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, MyError);
}
this.appcode= 123; // can add custom props
}
}
Вы можете использовать Error.captureStackTrace для фильтрации ненужной строки в трассировке стека.
function MyError() {
var tmp = Error.apply(this, arguments);
tmp.name = this.name = 'MyError';
this.message = tmp.message;
/*this.stack = */Object.defineProperty(this, 'stack', { // getter for more optimizy goodness
get: function() {
return tmp.stack;
}
});
Error.captureStackTrace(this, MyError); // showing stack trace up to instantiation of Error excluding it.
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 ...>
Мне очень нравится делать многократно используемые файлы.js, которые я помещаю практически в любой проект, в котором я участвую. Когда у меня будет время, он станет модулем.
За свои ошибки я создаю exceptions.js
файл и добавить его в мои файлы.
Вот пример кода внутри этого файла:
const util = require('util');
/**
* This exception should be used when some phat of code is not implemented.
* @param {String} message Error message that will be used inside error.
* @inheritDoc Error
*/
function NotImplementedException(message) {
this.message = message;
Error.captureStackTrace(this, NotImplementedException);
}
util.inherits(NotImplementedException, Error);
NotImplementedException.prototype.name = 'NotImplementedException';
module.exports = {
NotImplementedException,
};
В других файлах моего проекта у меня должна быть эта обязательная строка в верхней части файла.
const Exceptions = require('./exceptions.js');
И чтобы использовать эту ошибку, вам просто нужно это.
const err = Exceptions.NotImplementedException(`Request token ${requestToken}: The "${operation}" from "${partner}" does not exist.`);
Пример полной реализации метода
const notImplemented = (requestToken, operation, partner) => {
logger.warn(`Request token ${requestToken}: To "${operation}" received from "${partner}"`);
return new Promise((resolve, reject) => {
const err = Exceptions.NotImplementedException(`Request token ${requestToken}: The "${operation}" from "${partner}" does not exist.`);
logger.error(err.message);
return reject(err);
});
};
Другой подход к этому - сделать новый экземпляр ошибки прототипом this
и, таким образом, вам не нужно знать, какие свойства копировать, что обходит проблемы, о которых B T говорил в конце своего ответа.
function MyError() {
if (this === undefined) {
throw TypeError("MyError must be called as a constructor");
}
let newErr = Error.apply(undefined, arguments);
Object.setPrototypeOf(newErr, MyError.prototype);
Object.setPrototypeOf(this, newErr);
}
MyError.prototype = Object.create(Error.prototype);
let me = new MyError("A terrible thing happened");
console.log(me instanceof MyError); // true
console.log(me instanceof Error); // true
console.log(me.message); // A terrible thing happened
И за мои деньги это немного аккуратнее. Но учтите, что Object.setPrototypeOf()
(или же object.__proto__ =
на реализациях, не поддерживающих ES6, которые поддерживают его), может быть очень медленным, поэтому, если вы используете эти ошибки на своих золотых путях, вы, возможно, не захотите этого делать.