Niestandardowy typ wyjątku

224

Czy mogę zdefiniować niestandardowe typy dla wyjątków zdefiniowanych przez użytkownika w JavaScript? Jeśli tak, jak mam to zrobić?

Manki
źródło
3
Strzec się. Według JavaScriptu za 10 minut nie dostaniesz śladu stosu, jeśli rzucisz rozpakowaną wartość.
Janus Troelsen,
exceptionsjs.com zapewnia możliwość tworzenia własnych wyjątków i zapewnia pewne wyjątki brakujących tym ArgumentException i NotImplemented domyślnie.
Steven Wexler,

Odpowiedzi:

232

Od WebReference :

throw { 
  name:        "System Error", 
  level:       "Show Stopper", 
  message:     "Error detected. Please contact the system administrator.", 
  htmlMessage: "Error detected. Please contact the <a href=\"mailto:[email protected]\">system administrator</a>.",
  toString:    function(){return this.name + ": " + this.message;} 
}; 
jon077
źródło
7
@ b.long Jest w „JavaScript: The Good Parts” (świetna książka IMO). Ten podgląd Książek Google pokazuje sekcję: books.google.com/books?id=PXa2bby0oQ0C&pg=PA32&lpg=PA32
orip
11
Dodanie metody toString sprawi, że będzie ładnie wyświetlana w konsoli javascript. bez niego pokazuje: Uncaught # <Object> z toString pokazuje: Uncaught System Error: Wykryto błąd. Skontaktuj się z administratorem systemu.
JDC
11
Nie pozwoli ci to na układanie śladów, chyba że odziedziczysz po Błędzie
Luke H
Jak można filtrować w obrębie bloku catch, aby pracować tylko z tym niestandardowym błędem?
Overdrivr
@overdrivr coś takiego jak catch (e) { if (e instanceof TypeError) { … } else { throw e; } }⦃⦄ lub catch (e) { switch (e.constructor) { case TypeError: …; break; default: throw e; }⦃⦄.
sam boosalis,
92

Należy utworzyć niestandardowy wyjątek, który prototypowo dziedziczy po błędzie. Na przykład:

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;

Jest to w zasadzie uproszczona wersja tego, co zniechęcony opublikował powyżej, z ulepszeniem, które ślady stosu działają w przeglądarce Firefox i innych przeglądarkach. Spełnia te same testy, które opublikował:

Stosowanie:

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

I będzie się zachowywać, należy się spodziewać:

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!
asselin
źródło
80

Możesz wdrożyć własne wyjątki i ich obsługę, na przykład jak tutaj:

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

Istnieje inna składnia do przechwytywania wpisanego wyjątku, chociaż nie działa to w każdej przeglądarce (na przykład nie w 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");
}
Siergiej Iliński
źródło
2
Witryna MSN zawiera to ostrzeżenie o wyłapywaniu warunków: niestandardowa Ta funkcja jest niestandardowa i nie znajduje się na standardowym torze. Nie używaj go w witrynach produkcyjnych z dostępem do Internetu: nie będzie działać dla każdego użytkownika. Mogą również występować duże niezgodności między implementacjami, a zachowanie może ulec zmianie w przyszłości.
Lawrence Dol
40

Tak. Możesz rzucać cokolwiek zechcesz: liczby całkowite, łańcuchy, obiekty, cokolwiek. Jeśli chcesz rzucić obiekt, po prostu stwórz nowy obiekt, tak jak zrobiłbyś go w innych okolicznościach, a następnie rzuć nim. Odwołanie do Mozilli w JavaScript zawiera kilka przykładów.

Rob Kennedy
źródło
26
function MyError(message) {
 this.message = message;
}

MyError.prototype = new Error;

Pozwala to na użycie takie jak ..

try {
  something();
 } catch(e) {
  if(e instanceof MyError)
   doSomethingElse();
  else if(e instanceof Error)
   andNowForSomethingCompletelyDifferent();
}
Morgan ARR Allen
źródło
Czy ten krótki przykład nie działałby dokładnie w ten sam sposób, nawet gdybyś nie odziedziczył prototypu Error? Nie jest dla mnie jasne, co zyskuje cię w tym przykładzie.
EleventyOne
1
Nie, e instanceof Errorbyłoby fałszywe.
Morgan ARR Allen
W rzeczy samej. Ale ponieważ e instanceof MyErrorbyłoby to prawdą, else if(e instanceof Error)oświadczenie nigdy nie zostanie ocenione.
EleventyOne
Racja, to tylko przykład działania tego stylu try / catch. Gdzie else if(e instanceof Error)byłby ostatni haczyk. Najprawdopodobniej następuje prosty else(którego nie uwzględniłem). Coś w rodzaju default:instrukcji switch, ale zawiera błędy.
Morgan ARR Allen
15

W skrócie:

Opcja 1: użyj rozszerzenia Babel-plugin-transform-builtin-extension

Opcja 2: zrób to sam (zainspirowany tą samą biblioteką)

    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);
  • Jeśli używasz czystego 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;
    }
  • Alternatywnie: użyj szkieletu Classtrophobic

Wyjaśnienie:

Dlaczego rozszerzenie klasy Error za pomocą ES6 i Babel jest problemem?

Ponieważ wystąpienie CustomError nie jest już rozpoznawane jako takie.

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

W rzeczywistości, z oficjalnej dokumentacji Babel, to nie może przedłużyć jakikolwiek wbudowanych klas JavaScript , takich jak Date, Array, DOMlub Error.

Problem opisano tutaj:

Co z innymi odpowiedziami SO?

Wszystkie podane odpowiedzi rozwiązują instanceofproblem, ale tracisz regularny błąd console.log:

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

Podczas korzystania z metody wspomnianej powyżej nie tylko naprawiasz instanceofproblem, ale także utrzymujesz regularny błąd console.log:

console.log(new CustomError('test'));
// output:
// Error: test
//     at CustomError (<anonymous>:2:32)
//     at <anonymous>:1:5
JBE
źródło
11

Oto, w jaki sposób można tworzyć niestandardowe błędy o całkowicie identycznym Errorzachowaniu natywnym . Technika ta działa tylko w Chrome i node.js teraz. Nie polecałbym go również używać, jeśli nie rozumiesz, co on robi.

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

})();

Jako wynik otrzymujemy

/**
 * 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)

Następnie możesz użyć tego w następujący sposób:

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

I używaj NotImplementedErrortak, jakbyś Error:

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

I będzie się zachowywać, należy się spodziewać:

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!

Zauważ, że to error.stackdziała absolutnie poprawnie i nie będzie zawierać NotImplementedErrorwywołania konstruktora (dzięki v8 Error.captureStackTrace()).

Uwaga. Jest brzydka eval(). Jedynym powodem, dla którego jest używany, jest poprawność err.constructor.name. Jeśli go nie potrzebujesz, możesz nieco uprościć wszystko.

rozproszony
źródło
2
Error.apply(self, arguments)jest określony, aby nie działać . Zamiast tego sugeruję skopiowanie śladu stosu, który jest kompatybilny z różnymi przeglądarkami.
Kornel,
11

Często używam podejścia z dziedziczeniem prototypowym. Przesłonięcie toString()daje tę przewagę, że narzędzia takie jak Firebug będą rejestrować rzeczywiste informacje zamiast [object Object]w konsoli w celu przechwycenia wyjątków.

Służy instanceofdo określania rodzaju wyjątku.

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

Exception.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();
Matthias
źródło
8

ES6

Dzięki nowej klasie i rozszerzaniu słów kluczowych jest teraz o wiele łatwiej:

class CustomError extends Error {
  constructor(message) {
    super(message);
    //something
  }
}
Brunno
źródło
7

Użyj instrukcji throw .

JavaScript nie dba o typ wyjątku (tak jak Java). JavaScript tylko zauważa, istnieje wyjątek, a kiedy go złapiesz, możesz „spojrzeć” na to, co mówi wyjątek.

Jeśli masz różne typy wyjątków, które musisz zgłosić, sugeruję użycie zmiennych zawierających ciąg / obiekt wyjątku, tj. Komunikat. Tam, gdzie jest to potrzebne, użyj „wyrzuć mój wyjątek”, a następnie złap wychwycony wyjątek od mojego wyjątku.

Xn0vv3r
źródło
1

Zobacz ten przykład w MDN.

Jeśli musisz zdefiniować wiele błędów (przetestuj kod tutaj !):

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;

Kod jest w większości kopiowany z: Jaki jest dobry sposób na rozszerzenie Błąd w JavaScript?

Peter Tseng
źródło
1

Alternatywa dla odpowiedzi asselin do użytku z klasami ES2015

class InvalidArgumentException extends Error {
    constructor(message) {
        super();
        Error.captureStackTrace(this, this.constructor);
        this.name = "InvalidArgumentException";
        this.message = message;
    }
}
Pan Tsjolder
źródło
1
//create error object
var error = new Object();
error.reason="some reason!";

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

Teraz ustawiamy dodając przyczynę lub dowolne właściwości, które chcemy, do obiektu błędu i pobieramy go. Uczyniając błąd bardziej rozsądnym.

kolega
źródło