Наследование от объекта 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, которые поддерживают его), может быть очень медленным, поэтому, если вы используете эти ошибки на своих золотых путях, вы, возможно, не захотите этого делать.

Другие вопросы по тегам