Який хороший спосіб розширити помилку в JavaScript?


368

Я хочу кинути деякі речі в свій JS-код, і я хочу, щоб вони були instanceof Error, але я також хочу, щоб вони були чимось іншим.

У Python, як правило, можна було б підкласи Виняток.

Що потрібно зробити в JS?

Відповіді:


217

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

Сафарі - помітний виняток. Немає 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, оскільки підкласи повинні поширювати клас.
aaaidan

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;замість цього.
Константин Ван

52

Коротко:

  • Якщо ви використовуєте ES6 без транспіляторів :

    class CustomError extends Error { /* ... */}
  • Якщо ви використовуєте транспілятор Babel :

Варіант 1: використовувати babel-плагін-перетворення-вбудований-розширення

Варіант 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;
    }
  • Альтернатива: використовувати кластерофобні рамки

Пояснення:

Чому розширення класу помилок за допомогою 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

1
class CustomError extends Error { /* ... */}неправильно обробляє специфічні для постачальника аргументи ( lineNumberтощо), "Розширення помилки в Javascript за допомогою синтаксису ES6" є специфічним для Babel, ваше рішення ES5 використовує, constі воно не обробляє власні аргументи.
Indolering

Дуже повна відповідь.
Даніеле Орландо

Це насправді забезпечує найбільш комплексне рішення і пояснює, чому потрібні різні твори. Велике спасибі JBE!
AndrewSouthpaw

Це допомогло мені вирішити проблему з успадкуванням від "Помилка". Це був два години кошмар!
Юліан Пінзару

2
Варто зауважити, що проблема з програмою console.log(new CustomError('test') instanceof CustomError);// falseбула справжньою на момент написання, але зараз вона була вирішена. Насправді проблема, пов’язана з відповіддю , була вирішена, і ми можемо перевірити правильність поведінки тут , вставивши код у REPL і побачивши, як він отримує правильну трансляцію для інстанції з правильним прототипом ланцюга.
Nobita

44

Редагувати: Прочитайте коментарі. Виявляється, це добре працює лише у V8 (Chrome / Node.JS). Моя мета полягала у створенні крос-браузерного рішення, яке б працювало у всіх браузерах, і забезпечувало слід стека там, де є підтримка.

Редагувати: Я створив цю спільноту Wiki, щоб дозволити більше редагування.

Рішення для 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вводить в оману. Це не робить спадкування, це робить їх одним класом .
Гельсіон

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.
Onur Yıldırım

Моя думка полягала в тому, що я не думаю, що вам насправді потрібно змінювати прототип тут. Ви можете просто використовувати рядок внизу ( 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 немає вбудованої логіки розширення / успадкування. Я впевнений, що плагін, який ви згадуєте, робить подібні речі.
Onur Yıldırım

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?
Олексій

1
Гей, @ Алекс, ти абсолютно прав! Це зараз виправлено. Дякую!
Віктор Шредер

18

Високоголосна відповідь Кресент Фреш вводить в оману. Хоча його попередження є недійсним, є й інші обмеження, до яких він не звертається.

По-перше, міркування в абзаці "Північ:" Півмісяця не мають сенсу. З цього пояснення випливає, що кодування "купок if (помилка instanceof MyError) else ..." є якимось обтяжливим або багатослівним порівняно з декількома заявами вилову. Кілька виразів заявки в одному блоці виловлення настільки ж стислі, як і кілька заяв на вилов - чистий і стислий код без жодних хитрощів. Це чудовий спосіб імітувати велику обробку помилок, характерних для підкидання Java.

WRT "з'являється властивість повідомлення підкласу не встановлюється", це не так, якщо ви використовуєте правильно сконструйований підклас помилок. Щоб створити власний підклас ErrorX Error, просто скопіюйте блок коду, починаючи з "var MyError =", змінивши одне слово "MyError" на "ErrorX". (Якщо ви хочете додати спеціальні методи до свого підкласу, дотримуйтесь тексту зразка).

Справжнє і суттєве обмеження підкласифікації помилок JavaScript полягає в тому, що для реалізацій JavaScript або відладчиків, які відстежують і повідомляють про стеження стека та місцезнаходження імплантації, як FireFox, розташування у вашій власній реалізації підкласу помилок буде записано як точку інстанції клас, тоді як якщо ви використовували пряму помилку, це було б місце, де ви запустили "нову помилку (...)"). Користувачі IE, ймовірно, ніколи не помітять, але користувачі Fire Bug на FF побачать марні значення імен файлу та значення рядка, повідомлені поряд із цими Помилками, і їм доведеться детально простежувати стек до елемента №1, щоб знайти реальне місцезнаходження інстанції.


Чи правильно я зрозумів - що якщо ви не підкласируете і не використовуєте нову помилку (...) безпосередньо, то про ім’я та рядок файлу повідомляється належним чином? І ви в основному говорите, що на практиці (справжній, а не лише сексуальний або декоративний вид) помилки підкласифікації, не має сенсу?
jayarjo

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!"));

Це гарантує правильність всіх атрибутів, таких як стек, fileName lineNumber та ін.

Все, що вам потрібно зробити, це або скопіювати атрибути, або визначити для них getters. Ось приклад використання геттерів (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();
};

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/isissue/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;
  }
}

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

У "Вузлі", як інші говорили, це просто:

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';

Коли все буде сказано і зроблено, ви кидаєте новий виняток, і це виглядає приблизно так (я ліниво спробував це в інструментах хромованого розробника):

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)

5
Чи не переписує це властивість імені для ВСІХ випадків помилок?
панци

@panzi ви маєте рацію. Я виправив свою маленьку помилку. Дякую за голову вгору!
Готем К.

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, а також додає нові властивості помилок (включаючи .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 

Caveat: слід стека не є ідеальним, оскільки він приведе вас туди, де створена нова помилка, а не куди ви кинете. Це не велика справа в Chrome, оскільки він забезпечує вам повний слід стека безпосередньо в консолі. Але це більш проблематично, наприклад, у Firefox.


Це не вдається у справіm = new InvalidInputError(); dontThrowMeYet(m);
Ерік

@Eric Я згоден, але це здається досить невеликим обмеженням. Мені ніколи не потрібно було заздалегідь створювати об'єкт винятків (крім метапрограмування, таких як зразок мого коду вище). Це справді проблема для вас?
Джонатан Бенн

Так, поведінка, здається, однакова, тому я зміню свою відповідь. Я не на 100% задоволений слідом стека, який приводить вас до рядка "помилка вару" на 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 () ??).


Я зробив модуль, який може розширювати більшість звичайних старих об’єктів javascript, включаючи помилки. На цей момент він
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 ви можете обмежити оператор вилову лише ловом 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
}

1
Кинути предмет - не чудова ідея. У вас немає error.stack, стандартні інструменти не працюватимуть з ним і т. Д. Кращим способом було б додати властивості до екземпляра помилки, наприкладvar e = new Error(); e.type = "validation"; ...
timruffles

0

Спеціальний декоратор помилок

Це ґрунтується на відповіді Джорджа Бейлі , але розширює та спрощує оригінальну ідею. Він написаний на CoffeeScript, але легко перетворити на JavaScript. Ідея полягає у розширенні власної помилки Бейлі за допомогою декоратора, який завершує її, що дозволяє легко створювати нові власні помилки.

Примітка. Це працюватиме лише у 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 (повідомлення)

Змінюючи прототип після виклику помилки конструктора, нам не потрібно встановлювати стік викликів і повідомлення


0

У ES6 у Mohsen є чудова відповідь, яка задає ім'я, але якщо ви використовуєте TypeScript або якщо ви живете в майбутньому, де, сподіваюся, цю пропозицію для полів загального та приватного класу просунулась минулого етапу 3 як пропозиція і зробила її переходьте на етап 4 як частину ECMAScript / JavaScript, то, можливо, ви хочете знати, що це лише трохи коротше. На етапі 3 браузери починають реалізовувати функції, тож якщо ваш браузер підтримує його, код нижче може просто працювати. (Випробувано в новому браузері Edge v81, здається, він працює добре). Будьте попереджені, хоча ця функція є нестабільною на даний момент, і її слід використовувати обережно, і ви завжди повинні перевіряти підтримку браузера на нестабільні функції. Ця публікація в основному призначена для тих майбутніх мешканців, коли веб-переглядачі можуть це підтримувати. Щоб перевірити перевірку підтримкия можу використовувати . В даний час у нього є 66% підтримка на ринку браузерів, який є там, але це не так здорово, тому якщо ви дійсно хочете використовувати його зараз і не хочете чекати або використовувати транспілятор, як Babel, або щось на зразок TypeScript . MDNі

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

Порівняйте це з безіменною помилкою, яка при закиданні не запише це ім'я.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.