Какой хороший способ расширить Error в JavaScript?

369

Я хочу добавить некоторые вещи в мой код JS, и я хочу, чтобы они были экземпляром Error, но я также хочу, чтобы они были чем-то другим.

В Python, как правило, подкласс Exception.

Что нужно делать в JS?

Джош Гибсон
источник

Ответы:

218

Единственное стандартное поле объекта Error имеет messageсвойство. (См. Спецификацию языка MDN или EcmaScript, раздел 15.11). Все остальное зависит от платформы.

Среда Mosts устанавливает stackсвойство, но fileNameи lineNumberпрактически бесполезна для использования при наследовании.

Итак, минималистичный подход это:

function MyError(message) {
    this.name = 'MyError';
    this.message = message;
    this.stack = (new Error()).stack;
}
MyError.prototype = new Error;  // <-- remove this if you do not 
                                //     want MyError to be instanceof Error

Вы можете прослушать стек, снять с него ненужные элементы и извлечь информацию, такую ​​как fileName и lineNumber, но для этого требуется информация о платформе, на которой в данный момент работает JavaScript. В большинстве случаев это не нужно - и вы можете сделать это после смерти, если действительно хотите.

Safari является заметным исключением. Нет stackсвойства, но throwнаборы ключевых слов sourceURLи lineсвойства объекта, который бросается. Эти вещи гарантированно будут правильными.

Тестовые примеры, которые я использовал, можно найти здесь: JavaScript самостоятельно сделал Сравнение объектов ошибок .

Теро
источник
19
Вы можете переместить this.name = 'MyError' внешнюю часть функции и изменить ее на MyError.prototype.name = 'MyError'.
Дэниэл Бердсли
36
Это единственный правильный ответ здесь, хотя из соображений стиля я бы, наверное, так и написал. function MyError(message) { this.message = message; this.stack = Error().stack; } MyError.prototype = Object.create(Error.prototype); MyError.prototype.name = "MyError";
kybernetikos
11
Я бы MyError.prototype.constructor = MyErrorтоже добавил .
Бхарат Хатри
3
в ES6 Error.call (это сообщение); должен инициализировать this, верно?
4esn0k
4
MyError.prototype = Object.create (Error.prototype);
Робин, как птица
170

В ES6:

class MyError extends Error {
  constructor(message) {
    super(message);
    this.name = 'MyError';
  }
}

источник

Мохсен
источник
53
Стоит отметить, что это не работает, если вы используете функции ES6 через транспортер, такой как Babel, поскольку подклассы должны расширять класс.
ааайдан
6
Если вы используете babel и находитесь на узле> 5.x, вам не следует использовать предустановку es2015, но npmjs.com/package/babel-preset-node5 позволит вам использовать нативные расширения es6 и больше
Ace
2
Это лучший способ сделать это, когда это возможно. Пользовательские ошибки ведут себя как обычные ошибки как в Chrome, так и в Firefox (и, возможно, в других браузерах).
Мэтт Браун
2
Для браузеров, обратите внимание, что вы можете обнаружить поддержку классов во время выполнения и соответственно переключиться на неклассовую версию. Код обнаружения:var supportsClasses = false; try {eval('class X{}'); supportsClasses = true;} catch (e) {}
Мэтт Браун
31
Для удобства обслуживания используйте this.name = this.constructor.name;вместо этого.
Константин Ван
53

Короче говоря:

  • Если вы используете ES6 без транспортеров :

    class CustomError extends Error { /* ... */}
  • Если вы используете транспортер Babel :

Вариант 1: использовать babel-plugin-transform-builtin-extend

Вариант 2: сделай сам (по мотивам той же библиотеки)

    function CustomError(...args) {
      const instance = Reflect.construct(Error, args);
      Reflect.setPrototypeOf(instance, Reflect.getPrototypeOf(this));
      return instance;
    }
    CustomError.prototype = Object.create(Error.prototype, {
      constructor: {
        value: Error,
        enumerable: false,
        writable: true,
        configurable: true
      }
    });
    Reflect.setPrototypeOf(CustomError, Error);
  • Если вы используете чистый ES5 :

    function CustomError(message, fileName, lineNumber) {
      var instance = new Error(message, fileName, lineNumber);
      Object.setPrototypeOf(instance, Object.getPrototypeOf(this));
      return instance;
    }
    CustomError.prototype = Object.create(Error.prototype, {
      constructor: {
        value: Error,
        enumerable: false,
        writable: true,
        configurable: true
      }
    });
    if (Object.setPrototypeOf){
        Object.setPrototypeOf(CustomError, Error);
    } else {
        CustomError.__proto__ = Error;
    }
  • Альтернатива: использовать Classtrophobic framework

Объяснение:

Почему расширение класса Error с помощью ES6 и Babel является проблемой?

Потому что экземпляр CustomError больше не распознается как таковой.

class CustomError extends Error {}
console.log(new CustomError('test') instanceof Error);// true
console.log(new CustomError('test') instanceof CustomError);// false

В самом деле, из официальной документации Вавилонского, вы не можете расширить любой встроенные классы JavaScript , такие как Date, Array, DOMили Error.

Проблема описана здесь:

А как насчет других ответов SO?

Все приведенные ответы решают instanceofпроблему, но вы теряете обычную ошибку console.log:

console.log(new CustomError('test'));
// output:
// CustomError {name: "MyError", message: "test", stack: "Error↵    at CustomError (<anonymous>:4:19)↵    at <anonymous>:1:5"}

Принимая во внимание, что, используя метод, упомянутый выше, вы не только исправляете instanceofпроблему, но также сохраняете обычную ошибку console.log:

console.log(new CustomError('test'));
// output:
// Error: test
//     at CustomError (<anonymous>:2:32)
//     at <anonymous>:1:5
JBE
источник
1
class CustomError extends Error { /* ... */}неправильно обрабатывает специфичные для поставщика аргументы ( lineNumberи т. д.), «Расширение ошибки в Javascript с синтаксисом ES6» является специфическим для Babel, ваше решение ES5 использует constи не обрабатывает пользовательские аргументы.
Прогулка
Очень полный ответ.
Даниэле Орландо
Это фактически обеспечивает наиболее полное решение и объясняет, почему различные части необходимы. Большое спасибо JBE!
AndrewSouthpaw
Это помогло мне в решении проблемы с наследованием от «Ошибка». Это был двухчасовой кошмар!
Юлиан Пинзару
2
Стоит отметить, что проблема с console.log(new CustomError('test') instanceof CustomError);// falseбыла верной на момент написания, но теперь была решена. Фактически, проблема, связанная с ответом , была решена, и мы можем проверить правильное поведение здесь , вставив код в REPL и посмотрев, как он правильно передается для создания экземпляра с правильной цепочкой прототипов.
Нобита
44

Изменить: Пожалуйста, прочитайте комментарии. Оказывается, это хорошо работает только в V8 (Chrome / Node.JS). Моим намерением было создание кросс-браузерного решения, которое работало бы во всех браузерах и обеспечивало трассировку стека там, где есть поддержка.

Изменить: я сделал это вики сообщества, чтобы позволить больше редактирования.

Решение для V8 (Chrome / Node.JS), работает в Firefox и может быть изменено для корректной работы в IE. (см. конец поста)

function UserError(message) {
  this.constructor.prototype.__proto__ = Error.prototype // Make this an instanceof Error.
  Error.call(this) // Does not seem necessary. Perhaps remove this line?
  Error.captureStackTrace(this, this.constructor) // Creates the this.stack getter
  this.name = this.constructor.name; // Used to cause messages like "UserError: message" instead of the default "Error: message"
  this.message = message; // Used to set the message
}

Оригинальный пост "Покажи мне код!"

Укороченная версия:

function UserError(message) {
  this.constructor.prototype.__proto__ = Error.prototype
  Error.captureStackTrace(this, this.constructor)
  this.name = this.constructor.name
  this.message = message
}

Я держу this.constructor.prototype.__proto__ = Error.prototypeвнутри функции, чтобы держать весь код вместе. Но вы также можете заменить this.constructorна, UserErrorи это позволит вам переместить код за пределы функции, поэтому он вызывается только один раз.

Если вы идете по этому маршруту, убедитесь, что вы позвонили по этой линии до того, как в первый раз бросите UserError.

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

Совместимость браузера

Работает в Firefox и Chrome (и Node.JS) и выполняет все обещания.

Internet Explorer не работает в следующем

  • Ошибки не должны err.stackначинаться с, поэтому "это не моя вина".

  • Error.captureStackTrace(this, this.constructor) не существует, поэтому вам нужно сделать что-то еще, как

    if(Error.captureStackTrace) // AKA if not IE
        Error.captureStackTrace(this, this.constructor)
  • toStringперестает существовать, когда вы подкласс Error. Так что вам тоже нужно добавить.

    else
        this.toString = function () { return this.name + ': ' + this.message }
  • IE не будет считаться UserError, instanceof Errorесли вы не выполните следующее некоторое время, прежде чемthrow UserError

    UserError.prototype = Error.prototype
Джордж Бэйли
источник
16
Я не думаю, что Firefox на самом деле имеет captureStackTrace. Это расширение V8 и оно не определено в Firefox для меня, и я не могу найти в Интернете никаких ссылок на Firefox, поддерживающих его. (Спасибо, хотя!)
Джефф
5
Error.call(this)действительно ничего не делает, так как он возвращает ошибку, а не изменение this.
kybernetikos
1
Отлично работает на Node.js
Рудольф Мейеринг,
1
UserError.prototype = Error.prototypeвводит в заблуждение. Это не делает наследование, это делает их одним и тем же классом .
Halcyon
1
Я считаю, Object.setPrototypeOf(this.constructor.prototype, Error.prototype)что предпочтительнее this.constructor.prototype.__proto__ = Error.prototype, по крайней мере, для современных браузеров.
ChrisV
29

Чтобы избежать шаблона для каждого типа ошибки, я объединил мудрость некоторых решений в  createErrorTypeфункцию:

function createErrorType(name, init) {
  function E(message) {
    if (!Error.captureStackTrace)
      this.stack = (new Error()).stack;
    else
      Error.captureStackTrace(this, this.constructor);
    this.message = message;
    init && init.apply(this, arguments);
  }
  E.prototype = new Error();
  E.prototype.name = name;
  E.prototype.constructor = E;
  return E;
}

Затем вы можете легко определить новые типы ошибок следующим образом:

var NameError = createErrorType('NameError', function (name, invalidChar) {
  this.message = 'The name ' + name + ' may not contain ' + invalidChar;
});

var UnboundError = createErrorType('UnboundError', function (variableName) {
  this.message = 'Variable ' + variableName + ' is not bound';
});
Рубен Верборг
источник
Есть ли причина, по которой вам все еще нужна линия this.name = name;?
Питер Ценг
@PeterTseng Поскольку nameон уже установлен на прототип, он больше не нужен. Я удалил это. Спасибо!
Рубен Верборг
27

В 2018 году я думаю, что это лучший способ; который поддерживает IE9 + и современные браузеры.

ОБНОВЛЕНИЕ : см. Этот тест и репо для сравнения на разных реализациях.

function CustomError(message) {
    Object.defineProperty(this, 'name', {
        enumerable: false,
        writable: false,
        value: 'CustomError'
    });

    Object.defineProperty(this, 'message', {
        enumerable: false,
        writable: true,
        value: message
    });

    if (Error.hasOwnProperty('captureStackTrace')) { // V8
        Error.captureStackTrace(this, CustomError);
    } else {
        Object.defineProperty(this, 'stack', {
            enumerable: false,
            writable: false,
            value: (new Error(message)).stack
        });
    }
}

if (typeof Object.setPrototypeOf === 'function') {
    Object.setPrototypeOf(CustomError.prototype, Error.prototype);
} else {
    CustomError.prototype = Object.create(Error.prototype, {
        constructor: { value: CustomError }
    });
}

Также помните, что __proto__свойство устарело, что широко используется в других ответах.

Онур Йылдырым
источник
1
Почему вы используете setPrototypeOf()? По крайней мере, согласно MDN, обычно не рекомендуется использовать его, если вы можете выполнить то же самое, просто установив .prototypeсвойство в конструкторе (как вы делаете в elseблоке для браузеров, которые не имеют setPrototypeOf).
Мэтт Браун
Менять прототип объекта не рекомендуется всем вместе, а не setPrototypeOf. Но если вам все еще это нужно (как требует ОП), вам следует использовать встроенную методологию. Как указывает MDN, это считается правильным способом установки прототипа объекта. Другими словами, MDN говорит, что не изменяйте прототип (поскольку это влияет на производительность и оптимизацию), но, если вам нужно, используйте setPrototypeOf.
Онур Йылдырым
Моя точка зрения заключалась в том, что я не думаю, что вам действительно нужно менять прототип здесь. Вы можете просто использовать свою строку внизу ( CustomError.prototype = Object.create(Error.prototype)). Кроме того, Object.setPrototypeOf(CustomError, Error.prototype)это установка прототипа самого конструктора, а не указание прототипа для новых экземпляров CustomError. Во всяком случае, в 2016 году я думаю, что на самом деле есть лучший способ расширить ошибки, хотя я все еще выясняю, как использовать их вместе с Babel: github.com/loganfsmyth/babel-plugin-transform-builtin-extend/…
Мэтт Браун
CustomError.prototype = Object.create(Error.prototype)Также меняется прототип. Вы должны изменить его, поскольку в ES5 нет встроенной логики расширения / наследования. Я уверен, что плагин Babel, который вы упоминаете, делает подобные вещи.
Онур Йылдырым
1
Я создал суть, демонстрирующую, почему использование Object.setPrototypeOfздесь не имеет смысла, по крайней мере, не так, как вы его используете: gist.github.com/mbrowne/4af54767dcb3d529648f5a8aa11d6348 . Возможно, вы намеревались написать Object.setPrototypeOf(CustomError.prototype, Error.prototype)- это имело бы немного больше смысла (хотя все равно не давало никакой выгоды по сравнению с простыми настройками CustomError.prototype)
Мэтт Браун
19

Для полноты картины - просто потому, что ни один из предыдущих ответов не упомянул этот метод - если вы работаете с Node.js и вам не нужно заботиться о совместимости браузера, желаемый эффект довольно легко достичь с помощью встроенного inheritsиз utilмодуля ( официальные документы здесь ).

Например, предположим, что вы хотите создать собственный класс ошибок, который принимает код ошибки в качестве первого аргумента и сообщение об ошибке в качестве второго аргумента:

файл custom-error.js :

'use strict';

var util = require('util');

function CustomError(code, message) {
  Error.captureStackTrace(this, CustomError);
  this.name = CustomError.name;
  this.code = code;
  this.message = message;
}

util.inherits(CustomError, Error);

module.exports = CustomError;

Теперь вы можете создать экземпляр и передать / выбросить CustomError:

var CustomError = require('./path/to/custom-error');

// pass as the first argument to your callback
callback(new CustomError(404, 'Not found!'));

// or, if you are working with try/catch, throw it
throw new CustomError(500, 'Server Error!');

Обратите внимание, что в этом фрагменте трассировка стека будет иметь правильное имя файла и строку, а экземпляр ошибки будет иметь правильное имя!

Это происходит из-за использования captureStackTraceметода, который создает stackсвойство для целевого объекта (в данном случае, для CustomErrorэкземпляра). Для получения более подробной информации о том, как это работает, проверьте документацию здесь .

Виктор Шредер
источник
1
this.message = this.message;это неправильно или есть еще сумасшедшие вещи, которые я не знаю о JS?
Alex
1
Эй @ Алекс, ты совершенно прав! Это исправлено сейчас. Спасибо!
Виктор Шредер
18

Ответ Crescent Fresh с высоким рейтингом вводит в заблуждение. Хотя его предупреждения недействительны, есть и другие ограничения, которые он не рассматривает.

Во-первых, рассуждения в параграфе «Предостережения» Полумесяца не имеют смысла. Объяснение подразумевает, что кодирование "связки if (error instanceof MyError) else ..." является несколько обременительным или многословным по сравнению с несколькими операторами catch. Несколько операторов instanceof в одном блоке catch так же кратки, как и несколько операторов catch - чистый и лаконичный код без каких-либо хитростей. Это отличный способ эмулировать отличную обработку ошибок, характерную для бросаемых подтипов Java.

WRT «появляется сообщение, что свойство подкласса не устанавливается», это не тот случай, если вы используете правильно сконструированный подкласс Error. Чтобы создать собственный подкласс ErrorX Error, просто скопируйте блок кода, начинающийся с «var MyError =», заменив одно слово «MyError» на «ErrorX». (Если вы хотите добавить пользовательские методы в свой подкласс, следуйте примеру текста).

Реальное и существенное ограничение подклассов ошибок JavaScript заключается в том, что для реализаций JavaScript или отладчиков, которые отслеживают и сообщают о трассировке стека и расположении экземпляров, таких как FireFox, местоположение в вашей собственной реализации подкласса ошибок будет записываться как точка создания экземпляров. класс, тогда как если бы вы использовали прямую ошибку, это будет место, где вы запустили «новая ошибка (...)»). Пользователи IE, вероятно, никогда этого не заметят, но пользователи Fire Bug в FF увидят бесполезные значения имен файлов и номеров строк, сообщаемые вместе с этими ошибками, и им придется детализировать трассировку стека до элемента # 1, чтобы найти реальное местоположение экземпляра.

Blaine
источник
Правильно ли я понял - если вы не подкласс и не используете новый Error (...) напрямую, то имя файла и строка сообщаются правильно? И вы в основном говорите, что на практике (реальной, а не просто сексуальной или декоративной) подклассификации ошибок, не имеет смысла?
Джаярджо
6
Этот ответ несколько сбивает с толку, так как Crescent Fresh'sбыл удален!
Питер
это все еще так? developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Строка номер 2 не там, где был вызван новый
Мухаммед Умер
13

Как насчет этого решения?

Вместо того, чтобы выдавать свою собственную ошибку, используя:

throw new MyError("Oops!");

Вы бы обернули объект Error (вроде декоратора):

throw new MyError(Error("Oops!"));

Это гарантирует, что все атрибуты являются правильными, такие как стек, имя_файла lineNumber и т. Д.

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

function MyError(wrapped)
{
        this.wrapped = wrapped;
        this.wrapped.name = 'MyError';
}

function wrap(attr)
{
        Object.defineProperty(MyError.prototype, attr, {
                get: function()
                {
                        return this.wrapped[attr];
                }
        });
}

MyError.prototype = Object.create(Error.prototype);
MyError.prototype.constructor = MyError;

wrap('name');
wrap('message');
wrap('stack');
wrap('fileName');
wrap('lineNumber');
wrap('columnNumber');

MyError.prototype.toString = function()
{
        return this.wrapped.toString();
};
JoWie
источник
1
Я выпустил это решение в виде пакета npm: npmjs.com/package/throwable
JoWie
Невероятно элегантное решение, спасибо, что поделились! Один из вариантов: new MyErr (arg1, arg2, new Error())и в конструкторе MyErr мы используем Object.assignдля присваивания свойств последнего аргументаthis
Peeyush Kushwaha
Мне это нравится. Вы обходите ограничение, используя инкапсуляцию вместо наследования.
Дженни О'Рейли
13

Как говорят некоторые люди, с ES6 это довольно просто:

class CustomError extends Error { }

Я попробовал это в своем приложении (Angular, Typescript), и оно просто не сработало. Через некоторое время я обнаружил, что проблема исходит от Typescript: O

См. Https://github.com/Microsoft/TypeScript/issues/13965.

Это очень беспокоит, потому что если вы делаете:

class CustomError extends Error {}


try {
  throw new CustomError()
} catch(e) {
  if (e instanceof CustomError) {
    console.log('Custom error');
  } else {
    console.log('Basic error');
  }
}

В узле или непосредственно в вашем браузере будет отображаться: Custom error

Попробуйте запустить это с Typescript в вашем проекте на площадке Typescript, он будет отображаться Basic error...

Решение состоит в том, чтобы сделать следующее:

class CustomError extends Error {
  // we have to do the following because of: https://github.com/Microsoft/TypeScript/issues/13965
  // otherwise we cannot use instanceof later to catch a given type
  public __proto__: Error;

  constructor(message?: string) {
    const trueProto = new.target.prototype;
    super(message);

    this.__proto__ = trueProto;
  }
}
maxime1992
источник
10

Мое решение более простое, чем другие ответы, и не имеет недостатков.

Он сохраняет цепочку прототипов Error и все свойства Error, не требуя специальных знаний о них. Он был протестирован в Chrome, Firefox, Node и IE11.

Единственным ограничением является дополнительная запись в верхней части стека вызовов. Но это легко игнорируется.

Вот пример с двумя пользовательскими параметрами:

function CustomError(message, param1, param2) {
    var err = new Error(message);
    Object.setPrototypeOf(err, CustomError.prototype);

    err.param1 = param1;
    err.param2 = param2;

    return err;
}

CustomError.prototype = Object.create(
    Error.prototype,
    {name: {value: 'CustomError', enumerable: false}}
);

Пример использования:

try {
    throw new CustomError('Something Unexpected Happened!', 1234, 'neat');
} catch (ex) {
    console.log(ex.name); //CustomError
    console.log(ex.message); //Something Unexpected Happened!
    console.log(ex.param1); //1234
    console.log(ex.param2); //neat
    console.log(ex.stack); //stacktrace
    console.log(ex instanceof Error); //true
    console.log(ex instanceof CustomError); //true
}

Для сред, которым требуется полифайл setPrototypeOf:

Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
    obj.__proto__ = proto;
    return obj;
};
Бен
источник
1
как задокументировано в моем ответе, это решение может вызвать проблему в Firefox или других браузерах, которые регистрируют только первую строку трассировки стека в консоли
Джонатан Бенн
Это единственный ответ, который я нашел, который хорошо работает с ES5 (использование классов ES6 тоже хорошо работает). В Chromium DevTools ошибки отображаются намного лучше, чем в других ответах.
Бен Габлер
Примечание: если вы используете это решение с TypeScript, вы должны запустить throw CustomError('err')вместоthrow new CustomError('err')
Бен Габлер
8

В приведенном выше примере Error.apply(также Error.call) ничего не делает для меня (Firefox 3.6 / Chrome 5). Обходной путь, который я использую:

function MyError(message, fileName, lineNumber) {
    var err = new Error();

    if (err.stack) {
        // remove one stack level:
        if (typeof(Components) != 'undefined') {
            // Mozilla:
            this.stack = err.stack.substring(err.stack.indexOf('\n')+1);
        }
        else if (typeof(chrome) != 'undefined' || typeof(process) != 'undefined') {
            // Google Chrome/Node.js:
            this.stack = err.stack.replace(/\n[^\n]*/,'');
        }
        else {
            this.stack = err.stack;
        }
    }
    this.message    = message    === undefined ? err.message    : message;
    this.fileName   = fileName   === undefined ? err.fileName   : fileName;
    this.lineNumber = lineNumber === undefined ? err.lineNumber : lineNumber;
}

MyError.prototype = new Error();
MyError.prototype.constructor = MyError;
MyError.prototype.name = 'MyError';
Панзи
источник
5

В Node, как говорили другие, все просто:

class DumbError extends Error {
    constructor(foo = 'bar', ...params) {
        super(...params);

        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, DumbError);
        }

        this.name = 'DumbError';

        this.foo = foo;
        this.date = new Date();
    }
}

try {
    let x = 3;
    if (x < 10) {
        throw new DumbError();
    }
} catch (error) {
    console.log(error);
}
Мухаммед Умер
источник
3

Я просто хочу добавить к тому, что уже заявили другие:

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

CustomError.prototype = Error.prototype;
CustomError.prototype.name = 'CustomError';

Таким образом, полный пример будет:

    var CustomError = function(message) {
        var err = new Error(message);
        err.name = 'CustomError';
        this.name = err.name;
        this.message = err.message;
        //check if there is a stack property supported in browser
        if (err.stack) {
            this.stack = err.stack;
        }
        //we should define how our toString function works as this will be used internally
        //by the browser's stack trace generation function
        this.toString = function() {
           return this.name + ': ' + this.message;
        };
    };
    CustomError.prototype = new Error();
    CustomError.prototype.name = 'CustomError';

Когда все сказано и сделано, вы бросаете свое новое исключение, и это выглядит так (я лениво пробовал это в инструментах Chrome Dev):

CustomError: Stuff Happened. GASP!
    at Error.CustomError (<anonymous>:3:19)
    at <anonymous>:2:7
    at Object.InjectedScript._evaluateOn (<anonymous>:603:39)
    at Object.InjectedScript._evaluateAndWrap (<anonymous>:562:52)
    at Object.InjectedScript.evaluate (<anonymous>:481:21)
Gautham C.
источник
5
Разве это не перезаписывает свойство name для всех экземпляров Error?
Panzi
@panzi ты прав. Я исправил свою маленькую ошибку. Спасибо за головы!
Gautham C.
3

Мои 2 цента:

Почему другой ответ?

а) Потому что доступ к Error.stackсвойству (как в некоторых ответах) имеет большое снижение производительности.

б) Потому что это только одна строка.

c) Потому что решение по адресу https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error , похоже, не сохраняет информацию стека.

//MyError class constructor
function MyError(msg){
    this.__proto__.__proto__ = Error.apply(null, arguments);
};

пример использования

http://jsfiddle.net/luciotato/xXyeB/

Что оно делает?

this.__proto__.__proto__есть MyError.prototype.__proto__, поэтому он устанавливает __proto__для ВСЕХ ВСТРОЕНИЙ MyError конкретную вновь созданную ошибку. Он сохраняет свойства и методы класса MyError, а также помещает новые свойства Error (включая .stack) в __proto__цепочку.

Очевидная проблема:

Вы не можете иметь более одного экземпляра MyError с полезной информацией стека.

Не используйте это решение, если вы не полностью понимаете, что this.__proto__.__proto__=делает.

Лусио М. Тато
источник
2

Поскольку исключения JavaScript трудно подклассифицировать, я не подклассифицирую. Я просто создаю новый класс исключения и использую внутри него ошибку. Я изменяю свойство Error.name, чтобы оно выглядело как мое пользовательское исключение на консоли:

var InvalidInputError = function(message) {
    var error = new Error(message);
    error.name = 'InvalidInputError';
    return error;
};

Вышеуказанное новое исключение может быть сгенерировано так же, как обычная ошибка, и оно будет работать, как и ожидалось, например:

throw new InvalidInputError("Input must be a string");
// Output: Uncaught InvalidInputError: Input must be a string 

Предостережение: трассировка стека не идеальна, так как она приведет вас туда, где создана новая Ошибка, а не туда, куда вы выбросили. Это не имеет большого значения для Chrome, потому что он обеспечивает полную трассировку стека прямо в консоли. Но это более проблематично, например, в Firefox.

Джонатан Бенн
источник
Это терпит неудачу в случаеm = new InvalidInputError(); dontThrowMeYet(m);
Эрик
@ Эрик, я согласен, но это довольно маленькое ограничение. Мне никогда не приходилось создавать экземпляры объекта исключения заранее (исключая использование метапрограммирования, как в моем примере кода выше). Это действительно проблема для вас?
Джонатан Бенн
Да, поведение кажется таким же, поэтому я изменю свой ответ. Я не на 100% удовлетворен трассировкой стека, которая приводит вас к строке «var error» в Firefox и Chrome
Джонатан Бенн
1
@JonathanBenn Я очень опоздал на вечеринку, так что, может быть, вы уже подобрали это. Я часто создаю экземпляр объекта исключения, когда использую асинхронное программирование и обещания. После имен @ Эрика, я часто использую m = new ...тогда Promise.reject(m). В этом нет необходимости, но код легче читать.
BaldEagle
1
@JonathanBenn: (хе-хе) в 14 октября вы, кажется, думали, что создание объекта исключения перед его выбрасыванием будет редкостью. Я привел пример того, как я это делаю. Я не скажу, что это часто, но это удобно, когда я этого хочу. И мой код более читабелен, потому что создание экземпляров происходит в одной строке, а отклонение - в другой. Я надеюсь, что это делает!
BaldEagle
2

Как указано в ответе Мохсена, в ES6 можно расширять ошибки, используя классы. Это намного проще, и их поведение в большей степени согласуется с собственными ошибками ... но, к сожалению, не так просто использовать это в браузере, если вам нужна поддержка браузеров до ES6. Ниже приведены некоторые заметки о том, как это может быть реализовано, но пока я предлагаю относительно простой подход, который включает некоторые из лучших предложений из других ответов:

function CustomError(message) {
    //This is for future compatibility with the ES6 version, which
    //would display a similar message if invoked without the
    //`new` operator.
    if (!(this instanceof CustomError)) {
        throw new TypeError("Constructor 'CustomError' cannot be invoked without 'new'");
    }
    this.message = message;

    //Stack trace in V8
    if (Error.captureStackTrace) {
       Error.captureStackTrace(this, CustomError);
    }
    else this.stack = (new Error).stack;
}
CustomError.prototype = Object.create(Error.prototype);
CustomError.prototype.name = 'CustomError';

В ES6 это так просто, как:

class CustomError extends Error {}

... и вы можете обнаружить поддержку классов ES6 с помощью try {eval('class X{}'), но вы получите синтаксическую ошибку, если попытаетесь включить версию ES6 в скрипт, загружаемый старыми браузерами. Поэтому единственным способом поддержки всех браузеров будет динамическая загрузка отдельного скрипта (например, через AJAX или eval()) для браузеров, которые поддерживают ES6. Еще одним осложнением является то, что eval()он поддерживается не во всех средах (из-за политик безопасности содержимого), что может или не может быть соображением для вашего проекта.

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

Есть еще один подход, который некоторые люди, возможно, захотят рассмотреть, который заключается в использовании, Object.setPrototypeOf()где это возможно, для создания объекта ошибки, который является экземпляром вашего пользовательского типа ошибки, но который выглядит и ведет себя больше как собственная ошибка в консоли (благодаря ответу Бена за рекомендацию). Вот мой взгляд на этот подход: https://gist.github.com/mbrowne/fe45db61cea7858d11be933a998926a8 . Но, учитывая, что однажды мы сможем просто использовать ES6, лично я не уверен, что сложность такого подхода того стоит.

Мэтт Браун
источник
1

Способ сделать это правильно - вернуть результат применения из конструктора, а также установить прототип обычным сложным способом javascripty:

function MyError() {
    var tmp = Error.apply(this, arguments);
    tmp.name = this.name = 'MyError'

    this.stack = tmp.stack
    this.message = tmp.message

    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 () ??).

BT
источник
Я сделал модуль, который может расширять большинство обычных старых объектов JavaScript, включая ошибки. Это довольно зрелый момент на этом этапе github.com/fresheneesz/proto
BT
1

Фрагмент показывает все это.

function add(x, y) {
      if (x && y) {
        return x + y;
      } else {
        /**
         * 
         * the error thrown will be instanceof Error class and InvalidArgsError also
         */
        throw new InvalidArgsError();
        // throw new Invalid_Args_Error(); 
      }
    }

    // Declare custom error using using Class
    class Invalid_Args_Error extends Error {
      constructor() {
        super("Invalid arguments");
        Error.captureStackTrace(this);
      }
    }

    // Declare custom error using Function
    function InvalidArgsError(message) {
      this.message = `Invalid arguments`;
      Error.captureStackTrace(this);
    }
    // does the same magic as extends keyword
    Object.setPrototypeOf(InvalidArgsError.prototype, Error.prototype);

    try{
      add(2)
    }catch(e){
      // true
      if(e instanceof Error){
        console.log(e)
      }
      // true
      if(e instanceof InvalidArgsError){
        console.log(e)
      }
    }
Амарприт Сингх
источник
0

Я бы сделал шаг назад и подумал, почему ты хочешь это сделать? Я думаю, что дело в разных ошибках по-разному.

Например, в Python вы можете ограничить оператор catch только catch MyValidationError, и, возможно, вы захотите сделать что-то подобное в javascript.

catch (MyValidationError e) {
    ....
}

Вы не можете сделать это в JavaScript. Там будет только один блок поймать. Вы должны использовать оператор if для определения ошибки.

catch(e) { if(isMyValidationError(e)) { ... } else { // maybe rethrow? throw e; } }

Я думаю, что вместо этого бросил бы необработанный объект с типом, сообщением и любыми другими свойствами, которые вы считаете подходящими.

throw { type: "validation", message: "Invalid timestamp" }

И когда вы ловите ошибку:

catch(e) {
    if(e.type === "validation") {
         // handle error
    }
    // re-throw, or whatever else
}
Hasen
источник
1
Бросать предмет - не лучшая идея. У вас нет error.stack, стандартный инструмент не будет работать с ним и т. Д. И т. Д. Лучше было бы добавить свойства к экземпляру ошибки, например,var e = new Error(); e.type = "validation"; ...
timruffles
0

Декоратор пользовательских ошибок

Это основано на ответе Джорджа Бэйли , но расширяет и упрощает первоначальную идею. Он написан на CoffeeScript, но его легко конвертировать в JavaScript. Идея заключается в расширении пользовательской ошибки Bailey с помощью декоратора, который оборачивает ее, позволяя вам легко создавать новые пользовательские ошибки.

Примечание: это будет работать только в V8. Нет поддержки для Error.captureStackTraceдругих сред.

определять

Декоратор принимает имя для типа ошибки и возвращает функцию, которая принимает сообщение об ошибке и включает имя ошибки.

CoreError = (@message) ->

    @constructor.prototype.__proto__ = Error.prototype
    Error.captureStackTrace @, @constructor
    @name = @constructor.name

BaseError = (type) ->

    (message) -> new CoreError "#{ type }Error: #{ message }"

использование

Теперь просто создавать новые типы ошибок.

StorageError   = BaseError "Storage"
SignatureError = BaseError "Signature"

Для забавы вы можете теперь определить функцию, которая выдает a, SignatureErrorесли она вызывается со слишком большим количеством аргументов.

f = -> throw SignatureError "too many args" if arguments.length

Это было проверено довольно хорошо и, кажется, отлично работает на V8, поддерживая отслеживание, положение и т. Д.

Примечание. Использование newнеобязательно при создании пользовательской ошибки.

Карл Смит
источник
0

если вы не заботитесь о производительности из-за ошибок, это самое маленькое, что вы можете сделать

Object.setPrototypeOf(MyError.prototype, Error.prototype)
function MyError(message) {
    const error = new Error(message)
    Object.setPrototypeOf(error, MyError.prototype);
    return error
}

Вы можете использовать его без новых просто MyError (сообщение)

Изменяя прототип после вызова конструктора Error, нам не нужно устанавливать callstack и сообщение

bormat
источник
0

У Мохсена есть отличный ответ выше в ES6, который задает имя, но если вы используете TypeScript или живете в будущем, где, как мы надеемся, это предложение для полей открытого и закрытого классов перешло стадию 3 как предложение и сделало его на этапе 4 как часть ECMAScript / JavaScript, возможно, вы захотите узнать, что это будет немного короче. На этапе 3 браузеры начинают реализовывать функции, поэтому, если ваш браузер поддерживает их, приведенный ниже код может сработать. (Протестировано в новом браузере Edge v81, похоже, работает нормально). Имейте в виду, что на данный момент это нестабильная функция, которую следует использовать с осторожностью, и вы всегда должны проверять поддержку браузером нестабильных функций. Этот пост предназначен в основном для тех будущих жителей, когда браузеры могут это поддерживать. Чтобы проверить поддержку проверить MDNи могу ли я использовать . В настоящее время он пользуется поддержкой 66% на рынке браузеров, что не так уж и здорово, поэтому, если вы действительно хотите использовать его сейчас и не хотите ждать, либо используйте транспортер, такой как Babel, или что-то вроде TypeScript .

class EOFError extends Error { 
  name="EOFError"
}
throw new EOFError("Oops errored");

Сравните это с безымянной ошибкой, которая, если ее выбросить, не запишет ее имя.

class NamelessEOFError extends Error {}
throw new NamelessEOFError("Oops errored");

Джон
источник