Унаследовано от объекта 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);
  });
};