Спеціальний тип виключення


224

Чи можна визначити власні типи для визначених користувачем винятків у JavaScript? Якщо так, то як би я це зробив?


3
Остерігайся. Згідно з JavaScript за 10 хвилин, ви не отримаєте сліду стека, якщо ви кинете нерозмітне значення.
Янус Трольсен

exceptionsjs.com надає можливість створювати власні винятки та надає деякі відсутні винятки, зокрема ArgumentException та NotImplemented за замовчуванням.
Стівен Векслер

Відповіді:


232

З веб- референції :

throw { 
  name:        "System Error", 
  level:       "Show Stopper", 
  message:     "Error detected. Please contact the system administrator.", 
  htmlMessage: "Error detected. Please contact the <a href=\"mailto:sysadmin@acme-widgets.com\">system administrator</a>.",
  toString:    function(){return this.name + ": " + this.message;} 
}; 

7
@ b.long Це в "JavaScript: хороші частини" (чудова книга IMO). У цьому попередньому перегляді Книг Google відображається розділ: books.google.com/books?id=PXa2bby0oQ0C&pg=PA32&lpg=PA32
приблизно

11
Додавання методу toString змусить його добре відображатися на консолі javascript. без цього показується як: Uncaught # <Object> з toString, він показує, як: Uncaught System Error: Помилка виявлена. Зверніться до системного адміністратора.
JDC

11
Це не дозволить вам стежити сліди, якщо ви не успадкуєте помилку
Luke H

Як можна фільтрувати в межах блоку лову, щоб працювати лише з цією власною помилкою?
Overdrivr

@overdrivr щось на зразок catch (e) { if (e instanceof TypeError) { … } else { throw e; } }⦃⦄ або catch (e) { switch (e.constructor) { case TypeError: …; break; default: throw e; }⦃⦄.
sam boosalis

92

Вам слід створити спеціальний виняток, який прототипно успадковується від помилки. Наприклад:

function InvalidArgumentException(message) {
    this.message = message;
    // Use V8's native method if available, otherwise fallback
    if ("captureStackTrace" in Error)
        Error.captureStackTrace(this, InvalidArgumentException);
    else
        this.stack = (new Error()).stack;
}

InvalidArgumentException.prototype = Object.create(Error.prototype);
InvalidArgumentException.prototype.name = "InvalidArgumentException";
InvalidArgumentException.prototype.constructor = InvalidArgumentException;

Це, в основному, спрощена версія того, що невдало розміщено вище, і вдосконалення, що сліди стека працюють у Firefox та інших браузерах. Це задовольняє тим же тестам, що і він:

Використання:

throw new InvalidArgumentException();
var err = new InvalidArgumentException("Not yet...");

І поводитиметься, як очікується:

err instanceof InvalidArgumentException          // -> true
err instanceof Error                             // -> true
InvalidArgumentException.prototype.isPrototypeOf(err) // -> true
Error.prototype.isPrototypeOf(err)               // -> true
err.constructor.name                             // -> InvalidArgumentException
err.name                                         // -> InvalidArgumentException
err.message                                      // -> Not yet...
err.toString()                                   // -> InvalidArgumentException: Not yet...
err.stack                                        // -> works fine!

80

Ви можете реалізувати власні винятки та їх обробку, наприклад, тут:

// define exceptions "classes" 
function NotNumberException() {}
function NotPositiveNumberException() {}

// try some code
try {
    // some function/code that can throw
    if (isNaN(value))
        throw new NotNumberException();
    else
    if (value < 0)
        throw new NotPositiveNumberException();
}
catch (e) {
    if (e instanceof NotNumberException) {
        alert("not a number");
    }
    else
    if (e instanceof NotPositiveNumberException) {
        alert("not a positive number");
    }
}

Існує ще один синтаксис для вилучення набраного винятку, хоча це працюватиме не в кожному браузері (наприклад, не в IE):

// define exceptions "classes" 
function NotNumberException() {}
function NotPositiveNumberException() {}

// try some code
try {
    // some function/code that can throw
    if (isNaN(value))
        throw new NotNumberException();
    else
    if (value < 0)
        throw new NotPositiveNumberException();
}
catch (e if e instanceof NotNumberException) {
    alert("not a number");
}
catch (e if e instanceof NotPositiveNumberException) {
    alert("not a positive number");
}

2
Веб-сайт MSN містить це попередження щодо вибору умов: нестандартне Ця функція є нестандартною та не відповідає стандартним стандартам. Не використовуйте його на виробничих сайтах, що стоять перед Інтернетом: це працюватиме не для кожного користувача. Також можуть бути великі несумісності між реалізаціями, і поведінка може змінитися в майбутньому.
Лоуренс Дол

40

Так. Ви можете кидати все, що завгодно: цілі числа, рядки, предмети, що завгодно. Якщо ви хочете кинути об’єкт, то просто створіть новий об’єкт так само, як ви створили би його за інших обставин, а потім киньте його. Посилання на Jazilla в Mozilla має кілька прикладів.


26
function MyError(message) {
 this.message = message;
}

MyError.prototype = new Error;

Це дозволяє використовувати, як ..

try {
  something();
 } catch(e) {
  if(e instanceof MyError)
   doSomethingElse();
  else if(e instanceof Error)
   andNowForSomethingCompletelyDifferent();
}

Чи не вдалося б цей короткий приклад точно так само, навіть якщо ви не успадкували прототип помилки? Мені незрозуміло, що ви отримуєте від цього прикладу.
EleventyOne

1
Ні, e instanceof Errorбуло б помилково.
Morgan ARR Allen

Справді. Але оскільки це e instanceof MyErrorбуло б правдою, else if(e instanceof Error)твердження ніколи не буде оцінено.
EleventyOne

Правильно, це лише приклад того, як буде працювати цей стиль проб / лову. Де else if(e instanceof Error)був би останній вилов. Ймовірно, слідує простий else(який я не включав). На зразок типу default:оператора перемикача, але для помилок.
Morgan ARR Allen

15

Коротко:

Варіант 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) {
      const 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

11

Ось як можна створювати власні помилки з абсолютно ідентичною Errorповедінкою рідного . Ця методика наразі працює лише в Chrome і node.js. Я також не рекомендував би використовувати його, якщо ви не розумієте, що це робить.

Error.createCustromConstructor = (function() {

    function define(obj, prop, value) {
        Object.defineProperty(obj, prop, {
            value: value,
            configurable: true,
            enumerable: false,
            writable: true
        });
    }

    return function(name, init, proto) {
        var CustomError;
        proto = proto || {};
        function build(message) {
            var self = this instanceof CustomError
                ? this
                : Object.create(CustomError.prototype);
            Error.apply(self, arguments);
            Error.captureStackTrace(self, CustomError);
            if (message != undefined) {
                define(self, 'message', String(message));
            }
            define(self, 'arguments', undefined);
            define(self, 'type', undefined);
            if (typeof init == 'function') {
                init.apply(self, arguments);
            }
            return self;
        }
        eval('CustomError = function ' + name + '() {' +
            'return build.apply(this, arguments); }');
        CustomError.prototype = Object.create(Error.prototype);
        define(CustomError.prototype, 'constructor', CustomError);
        for (var key in proto) {
            define(CustomError.prototype, key, proto[key]);
        }
        Object.defineProperty(CustomError.prototype, 'name', { value: name });
        return CustomError;
    }

})();

Як результат ми отримуємо

/**
 * name   The name of the constructor name
 * init   User-defined initialization function
 * proto  It's enumerable members will be added to 
 *        prototype of created constructor
 **/
Error.createCustromConstructor = function(name, init, proto)

Тоді ви можете використовувати його так:

var NotImplementedError = Error.createCustromConstructor('NotImplementedError');

І використовувати NotImplementedErrorяк би Error:

throw new NotImplementedError();
var err = new NotImplementedError();
var err = NotImplementedError('Not yet...');

І поводитиметься, як очікується:

err instanceof NotImplementedError               // -> true
err instanceof Error                             // -> true
NotImplementedError.prototype.isPrototypeOf(err) // -> true
Error.prototype.isPrototypeOf(err)               // -> true
err.constructor.name                             // -> NotImplementedError
err.name                                         // -> NotImplementedError
err.message                                      // -> Not yet...
err.toString()                                   // -> NotImplementedError: Not yet...
err.stack                                        // -> works fine!

Зауважте, що це error.stackпрацює абсолютне правильне і не буде включати NotImplementedErrorвиклик конструктора (завдяки v8 Error.captureStackTrace()).

Примітка. Там потворно eval(). Єдина причина, по якій він використовується - це виправити err.constructor.name. Якщо він вам не потрібен, ви можете трохи спростити все.


2
Error.apply(self, arguments)буде вказано не працювати . Я пропоную скопіювати слід стека замість того, що сумісний між веб-браузером.
Корнель

11

Я часто використовую підхід з успадкуванням прототипів. Переосмислення toString()дає вам перевагу в тому, що такі інструменти, як Firebug, записуватимуть фактичну інформацію замість [object Object]консолі для невдалих винятків.

Використовуйте instanceofдля визначення типу виключення.

main.js

// just an exemplary namespace
var ns = ns || {};

// include JavaScript of the following
// source files here (e.g. by concatenation)

var someId = 42;
throw new ns.DuplicateIdException('Another item with ID ' +
    someId + ' has been created');
// Firebug console:
// uncaught exception: [Duplicate ID] Another item with ID 42 has been created

Виняток.js

ns.Exception = function() {
}

/**
 * Form a string of relevant information.
 *
 * When providing this method, tools like Firebug show the returned 
 * string instead of [object Object] for uncaught exceptions.
 *
 * @return {String} information about the exception
 */
ns.Exception.prototype.toString = function() {
    var name = this.name || 'unknown';
    var message = this.message || 'no description';
    return '[' + name + '] ' + message;
};

DuplicateIdException.js

ns.DuplicateIdException = function(message) {
    this.name = 'Duplicate ID';
    this.message = message;
};

ns.DuplicateIdException.prototype = new ns.Exception();

8

ES6

З новим класом та розширенням ключових слів тепер набагато простіше:

class CustomError extends Error {
  constructor(message) {
    super(message);
    //something
  }
}

7

Використовуйте заяву кидка .

JavaScript не має значення, який тип винятку (як це робить Java). JavaScript лише зауважує, є виняток, і коли ви його зловите, ви можете "подивитися", що говорить виняток ".

Якщо у вас є різні типи винятків, які потрібно викинути, я б запропонував використовувати змінні, які містять рядок / об'єкт винятку, тобто повідомлення. Там, де вам це потрібно, використовуйте "кинути myException" і в улові, порівняйте спійманий виняток з myException.


1

Дивіться цей приклад у MDN.

Якщо вам потрібно визначити кілька помилок (тестуйте код тут !):

function createErrorType(name, initFunction) {
    function E(message) {
        this.message = message;
        if (Error.captureStackTrace)
            Error.captureStackTrace(this, this.constructor);
        else
            this.stack = (new Error()).stack;
        initFunction && initFunction.apply(this, arguments);
    }
    E.prototype = Object.create(Error.prototype);
    E.prototype.name = name;
    E.prototype.constructor = E;
    return E;
}
var InvalidStateError = createErrorType(
    'InvalidStateError',
    function (invalidState, acceptedStates) {
        this.message = 'The state ' + invalidState + ' is invalid. Expected ' + acceptedStates + '.';
    });

var error = new InvalidStateError('foo', 'bar or baz');
function assert(condition) { if (!condition) throw new Error(); }
assert(error.message);
assert(error instanceof InvalidStateError);  
assert(error instanceof Error); 
assert(error.name == 'InvalidStateError');
assert(error.stack);
error.message;

Код здебільшого скопійовано з: Який хороший спосіб розширити Помилка JavaScript?


1

Альтернатива відповіді asselin для використання з класами ES2015

class InvalidArgumentException extends Error {
    constructor(message) {
        super();
        Error.captureStackTrace(this, this.constructor);
        this.name = "InvalidArgumentException";
        this.message = message;
    }
}

1
//create error object
var error = new Object();
error.reason="some reason!";

//business function
function exception(){
    try{
        throw error;
    }catch(err){
        err.reason;
    }
}

Тепер ми встановлюємо причину чи будь-які властивості, які ми хочемо, до об’єкта помилки та отримуємо його. Роблячи помилку більш розумною.

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