Как создать пользовательскую ошибку в JavaScript? - PullRequest
192 голосов
/ 24 апреля 2009

Почему-то похоже, что делегирование конструктора не работает в следующем фрагменте:

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 в нативном конструкторе Error, о котором я не знаю?

Ответы [ 20 ]

187 голосов
/ 16 мая 2009

Обновите ваш код, чтобы назначить ваш прототип для Error.prototype и instanceof и ваши утверждения работают.

function NotImplementedError(message) {
    this.name = "NotImplementedError";
    this.message = (message || "");
}
NotImplementedError.prototype = Error.prototype;

Однако я бы просто бросил свой собственный объект и просто проверил имя свойства.

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);
}

Вывод консоли был таким.

undefined
ex1 instanceof NotImplementedError = true
ex1 instanceof Error = true
ex1.name = NotImplementedError
ex1.message = NotImplementedError message
Error
    at window.onload (http://fiddle.jshell.net/MwMEJ/show/:29:34)
ex1 instanceof NotImplementedError2 = true
ex1 instanceof Error = true
ex1.name = Error
ex1.message = NotImplementedError2 message

Это подтверждает, что «проблема», с которой я столкнулся, заключалась в том, что свойством стека ошибки был номер строки, где был создан new Error(), а не где throw e. Однако это может быть лучше, чем иметь побочный эффект линии NotImplementedError.prototype.name = "NotImplementedError", влияющей на объект Error.

Кроме того, обратите внимание на NotImplementedError2, когда я не устанавливаю .name явно, он равен «Ошибка». Однако, как упоминалось в комментариях, поскольку в этой версии для прототипа установлено значение new Error(), я мог бы установить NotImplementedError2.prototype.name = "NotImplementedError2" и быть в порядке.

85 голосов
/ 27 июля 2013

Все вышеперечисленные ответы ужасно ужасны - правда. Даже тот, с 107 ups! Реальный ответ здесь, ребята:

Наследование от объекта Error - где находится свойство сообщения?

TL; DR:

A. Причина, по которой message не установлен, заключается в том, что Error - это функция, которая возвращает новый объект Error и не манипулирует this любым способом.

B. Способ сделать это правильно - вернуть результат применения из конструктора, а также установить прототип обычным сложным способом 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();
*/

Вы, вероятно, могли бы сделать некоторую хитрость, чтобы перечислить все неперечислимые свойства ошибки tmp, чтобы установить их вместо явной установки только stack и message, но обман не поддерживается в ie 9

20 голосов
/ 22 декабря 2014

Если кому-то интересно, как создать пользовательскую ошибку и , получите трассировку стека:

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);
}
17 голосов
/ 14 июня 2017

В ES2015 вы можете использовать class, чтобы сделать это чисто:

class NotImplemented extends Error {
  constructor(message = "", ...args) {
    super(message, ...args);
    this.message = message + " has not yet been implemented.";
  }
}

Это не изменяет глобальный прототип Error, позволяет настраивать message, name и другие атрибуты и правильно захватывает стек. Это также довольно читабельно.

Конечно, вам может понадобиться такой инструмент, как babel, если ваш код будет работать в старых браузерах.

7 голосов
/ 12 августа 2011

Этот раздел стандарта может объяснить, почему вызов 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 голосов
/ 16 мая 2012

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

6 голосов
/ 20 ноября 2013
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;
4 голосов
/ 19 октября 2016

Согласно Joyent вы не должны связываться со свойством стека (которое я вижу во многих ответах, приведенных здесь), потому что оно отрицательно скажется на производительности. Вот что они говорят:

стек: как правило, не связывайтесь с этим. Даже не увеличивайте это. 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);
}

es6 версия:

class RError extends Error {
    constructor({name, message, cause}) {
        super();
        this.name = name;
        this.message = message;
        this.cause = cause;
    }
    [Symbol.iterator]() {
        let current = this;
        let done = false;
        const iterator = {
            next() {
                const val = current;
                if (done) {
                    return { value: val, done: true };
                }
                current = current.cause;
                if (!val.cause) {
                    done = true;
                }
                return { value: val, done: false };
            }
        };
        return iterator;
    }
    get why() {
        let _why = '';
        for (const e of this) {
            _why += `${_why.length ? ' <- ' : ''}${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);
}

Я поместил свое решение в модуль, вот оно: https://www.npmjs.com/package/rerror

2 голосов
/ 16 февраля 2015

Я использовал Constructor Pattern для создания нового объекта ошибки. Я определил цепочку прототипов , например, экземпляр 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;
}());

1017 * ИСПОЛЬЗОВАНИЕ * Конструктор 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; А вот так выглядит пользовательская ошибка: Custom error prototype chain

2 голосов
/ 31 мая 2012

Мне просто нужно было реализовать что-то подобное, и я обнаружил, что стек потерян в моей собственной реализации ошибок. Я должен был создать фиктивную ошибку и извлечь из нее стек:

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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...