Унаследовано от объекта 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
, но я не совсем понимаю, почему он еще не установлен в первую очередь.
Ответы
Ответ 1
а. Например, Raynos сказал: Причина message
не установлена, так это то, что Error
- это функция, которая возвращает новый объект Error и никак не манипулирует this
.
В. Способ сделать это правильно - установить результат применения конструктора на 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
и
- у stacktrace есть дополнительная строка, которая действительно не нужна.
Первая проблема может быть устранена путем повторения всех неперечислимых свойств ошибки с помощью трюка в этом ответе: Возможно ли получить неперечислимые унаследованные имена свойств объект?, но это не поддерживается, например, < 9. Вторая проблема может быть решена путем разрыва этой строки в трассировке стека, но я не уверен, как это сделать безопасно (возможно, просто удалив вторую строку e.stack.toString()??).
Обновление
Я создал библиотеку наследования, которая делает это ^ https://github.com/fresheneesz/proto
Ответ 2
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 }
});
Ответ 3
В 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 в node docs
Ответ 4
Вы можете использовать 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 ...>
Ответ 5
Другой подход к этому заключается в том, чтобы сделать новый экземпляр ошибки прототипом this
, и таким образом вам не нужно знать, какие свойства нужно копировать, что касается проблем, о которых BT говорил в конце своего ответа.
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 реализациях, которые его поддерживают) может быть очень медленно, поэтому, если вы используете эти ошибки на своих золотых дорожках, вы можете не захотеть этого делать.
Ответ 6
Мне нравится много сделать многоразовые файлы.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);
});
};