Jaki jest dobry sposób na rozszerzenie błędu w JavaScript?

368

Chcę wrzucić pewne rzeczy do mojego kodu JS i chcę, aby były one wystąpieniem Error, ale chcę też, aby były czymś innym.

W Pythonie zwykle należałoby podklasować wyjątek.

Co należy zrobić w JS?

Josh Gibson
źródło

Odpowiedzi:

217

Jedynym standardowym polem Obiekt błędu jest messagewłaściwość. (Zobacz MDN lub Specyfikacja języka EcmaScript, rozdział 15.11) Wszystko inne zależy od platformy.

Środowisk Mosts ustawić stackwłaściwość, ale fileNamei lineNumbersą praktycznie bezużyteczne być stosowane w spadku.

Zatem minimalistyczne podejście to:

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

Możesz powąchać stos, usunąć z niego niepożądane elementy i wyodrębnić informacje, takie jak fileName i lineNumber, ale zrobienie tego wymaga informacji o platformie, na której aktualnie działa JavaScript. W większości przypadków jest to niepotrzebne - i jeśli chcesz, możesz to zrobić pośmiertnie.

Safari jest godnym uwagi wyjątkiem. Nie ma żadnej stackwłaściwości, ale throwzestawy słów kluczowych sourceURLi linewłaściwości rzucanego obiektu. Te rzeczy są z pewnością poprawne.

Przypadki testowe, których użyłem, można znaleźć tutaj: własnoręcznie wykonane JavaScript Błąd porównywania obiektów .

Tero
źródło
19
Możesz przenieść this.name = 'MyError' zewnętrzną funkcję i zmienić ją na MyError.prototype.name = 'MyError'.
Daniel Beardsley,
36
To jedyna poprawna odpowiedź tutaj, chociaż ze względu na styl prawdopodobnie napisałbym to w ten sposób. function MyError(message) { this.message = message; this.stack = Error().stack; } MyError.prototype = Object.create(Error.prototype); MyError.prototype.name = "MyError";
kybernetikos
11
Też bym dodał MyError.prototype.constructor = MyError.
Bharat Khatri
3
w ES6 Error.call (ten komunikat); powinien się zainicjować this, prawda?
4esn0k
4
MyError.prototype = Object.create (Error.prototype);
Robin jak ptak
170

W ES6:

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

źródło

Mohsen
źródło
53
Warto wspomnieć, że to nie działa, jeśli używasz funkcji ES6 za pośrednictwem transpilatora, takiego jak Babel, ponieważ podklasy muszą rozszerzać klasę.
aaaidan
6
Jeśli używasz Babel i jesteś w węźle> 5.x, nie powinieneś używać ustawienia wstępnego es2015, ale npmjs.com/package/babel-preset-node5 pozwoli ci korzystać z natywnych rozszerzeń es6 plus więcej
Ace
2
To najlepszy sposób, aby to zrobić, gdy jest to możliwe. Błędy niestandardowe zachowują się bardziej jak zwykłe błędy zarówno w Chrome, jak i Firefox (i prawdopodobnie także w innych przeglądarkach).
Matt Browne,
2
W przypadku przeglądarek należy zauważyć, że można wykryć obsługę klas w czasie wykonywania i odpowiednio wrócić do wersji innej niż klasa. Kod detekcji:var supportsClasses = false; try {eval('class X{}'); supportsClasses = true;} catch (e) {}
Matt Browne
31
Aby ułatwić konserwację , użyj this.name = this.constructor.name;zamiast tego.
Константин Ван
51

W skrócie:

  • Jeśli używasz ES6 bez transpilatorów :

    class CustomError extends Error { /* ... */}
  • Jeśli używasz transpilatora Babel :

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) {
      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;
    }
  • 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
1
class CustomError extends Error { /* ... */}nie obsługuje poprawnie argumentów specyficznych dla dostawcy ( lineNumberitp.), „Błąd rozszerzenia w JavaScript ze składnią ES6” jest specyficzny dla Babel, twoje rozwiązanie ES5 używa consti nie obsługuje niestandardowych argumentów.
Indolering
Bardzo kompletna odpowiedź.
Daniele Orlando
To faktycznie zapewnia najbardziej kompleksowe rozwiązanie i wyjaśnia, dlaczego różne elementy są potrzebne. Dzięki bardzo JBE!
AndrewSouthpaw
Pomogło mi to rozwiązać problem z dziedziczeniem po „błędzie”. To był dwugodzinny koszmar!
Iulian Pinzaru
2
Warto zauważyć, że problem z console.log(new CustomError('test') instanceof CustomError);// falseprawdą był w momencie pisania, ale został już rozwiązany. W rzeczywistości problem powiązany z odpowiedzią został rozwiązany i możemy przetestować poprawne zachowanie tutaj i wklejając kod w REPL i sprawdzając, jak jest poprawnie transponowany, aby utworzyć instancję z poprawnym łańcuchem prototypów.
Nobita,
44

Edytować: Proszę przeczytać komentarze. Okazuje się, że działa to dobrze tylko w wersji V8 (Chrome / Node.JS). Moim zamiarem było udostępnienie rozwiązania dla różnych przeglądarek, które działałoby we wszystkich przeglądarkach, oraz zapewnienie śledzenia stosu tam, gdzie jest dostępna obsługa.

Edytować: Stworzyłem Wiki Wiki, aby umożliwić dalszą edycję.

Rozwiązanie dla V8 (Chrome / Node.JS), działa w Firefoksie i może być modyfikowane, aby działało głównie poprawnie w IE. (patrz koniec postu)

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
}

Oryginalny post na temat „Pokaż mi kod!”

Krótka wersja:

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

Trzymam się this.constructor.prototype.__proto__ = Error.prototypewewnątrz funkcji, aby zachować cały kod razem. Ale możesz też wymienićthis.constructor z UserErrori że pozwala na przenoszenie kodu na zewnątrz funkcji, więc jest wywoływana tylko raz.

Jeśli pójdziesz tą drogą, upewnij się, że nazywają tę linię przed pierwszym rzutem UserError.

To zastrzeżenie nie stosuje funkcji, ponieważ funkcje są tworzone najpierw, bez względu na kolejność. W ten sposób możesz bez problemu przenieść funkcję na koniec pliku.

Kompatybilność z przeglądarkami

Działa w Firefox i Chrome (i Node.JS) i spełnia wszystkie obietnice.

Internet Explorer nie działa w następujący sposób

  • Błędy nie muszą err.stackzaczynać się, więc „to nie moja wina”.

  • Error.captureStackTrace(this, this.constructor) nie istnieje, więc musisz zrobić coś innego

    if(Error.captureStackTrace) // AKA if not IE
        Error.captureStackTrace(this, this.constructor)
  • toStringprzestaje istnieć, gdy podklasujesz Error. Musisz także dodać.

    else
        this.toString = function () { return this.name + ': ' + this.message }
  • IE nie będzie uważał UserErrorza, instanceof Errorchyba że uruchomisz następujące przed tobąthrow UserError

    UserError.prototype = Error.prototype
George Bailey
źródło
16
Nie sądzę, że Firefox faktycznie ma captureStackTrace. Jest to rozszerzenie V8 i jest dla mnie niezdefiniowane w Firefoksie, ani nie mogę znaleźć w Internecie żadnych odniesień do przeglądarki Firefox, która go obsługuje. (Dzięki jednak!)
Geoff
5
Error.call(this)w rzeczywistości nic nie robi, ponieważ zwraca błąd zamiast modyfikować this.
kybernetikos
1
Działa świetnie dla Node.js
Rudolf Meijering
1
UserError.prototype = Error.prototypewprowadza w błąd. To nie robi dziedziczenia, czyni je tą samą klasą .
Halcyon
1
Uważam, że Object.setPrototypeOf(this.constructor.prototype, Error.prototype)jest to preferowane this.constructor.prototype.__proto__ = Error.prototype, przynajmniej dla obecnych przeglądarek.
ChrisV
29

Aby uniknąć blaszki dla każdego rodzaju błędu, połączyłem mądrość niektórych rozwiązań w  createErrorTypefunkcję:

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

Następnie możesz łatwo zdefiniować nowe typy błędów w następujący sposób:

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';
});
Ruben Verborgh
źródło
Czy istnieje powód, dla którego nadal potrzebujesz linii this.name = name;?
Peter Tseng
@PeterTseng Ponieważ namejest już ustawiony na prototypie, nie jest już potrzebny. Usunąłem to. Dzięki!
Ruben Verborgh,
27

W 2018 r roku uważam, że to najlepszy sposób; który obsługuje IE9 + i nowoczesne przeglądarki.

AKTUALIZACJA : Zobacz ten test i repozytorium, aby porównać różne implementacje.

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

Uważaj również na to, że __proto__własność jest przestarzała, co jest szeroko stosowane w innych odpowiedziach.

Onur Yıldırım
źródło
1
Dlaczego używasz setPrototypeOf()? Przynajmniej według MDN ogólnie odradza się go używać, jeśli możesz osiągnąć to samo, po prostu ustawiając .prototypewłaściwość w konstruktorze (tak jak robisz w elsebloku dla przeglądarek, które go nie mają setPrototypeOf).
Matt Browne,
Nie zaleca się zmiany prototypu obiektu razem setPrototypeOf. Ale jeśli nadal go potrzebujesz (jak prosi OP), powinieneś użyć wbudowanej metodologii. Jak wskazuje MDN, jest to uważane za właściwy sposób ustawienia prototypu obiektu. Innymi słowy, MDN mówi, że nie zmieniaj prototypu (ponieważ wpływa to na wydajność i optymalizację), ale jeśli musisz, użyj setPrototypeOf.
Onur Yıldırım
Chodzi mi o to, że nie sądzę, żebyś musiał tutaj zmienić prototyp. Możesz po prostu użyć swojej linii na dole ( CustomError.prototype = Object.create(Error.prototype)). Ponadto Object.setPrototypeOf(CustomError, Error.prototype)ustawia prototyp samego konstruktora, a nie określa prototyp dla nowych instancji CustomError. W każdym razie w 2016 roku uważam, że istnieje lepszy sposób na rozszerzenie błędów, chociaż wciąż zastanawiam się, jak używać tego razem z Babelem
Matt Browne
CustomError.prototype = Object.create(Error.prototype)zmienia również prototyp. Musisz to zmienić, ponieważ w ES5 nie ma wbudowanej logiki rozszerzania / dziedziczenia. Jestem pewien, że wspomniana wtyczka babel robi podobne rzeczy.
Onur Yıldırım
1
Stworzyłem listę pokazującą, dlaczego używanie Object.setPrototypeOfnie ma tutaj sensu, a przynajmniej nie w sposób, w jaki go używasz: gist.github.com/mbrowne/4af54767dcb3d529648f5a8aa11d6348 . Być może chciałeś napisać Object.setPrototypeOf(CustomError.prototype, Error.prototype)- to miałoby nieco więcej sensu (choć nadal nie przynosi żadnych korzyści w porównaniu do zwykłego ustawienia CustomError.prototype).
Matt Browne,
19

Dla kompletności - tylko dlatego, że żadna z poprzednich odpowiedzi nie wspominała o tej metodzie - jeśli pracujesz z Node.js i nie musisz się martwić kompatybilnością przeglądarki, pożądany efekt jest dość łatwy do osiągnięcia dzięki wbudowanemu inheritsz utilmodułu ( oficjalnych docs tutaj ).

Załóżmy na przykład, że chcesz utworzyć niestandardową klasę błędów, która przyjmuje kod błędu jako pierwszy argument, a komunikat o błędzie jako drugi argument:

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

Teraz możesz utworzyć instancję i przekazać / rzucić 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!');

Zauważ, że dzięki temu fragmentowi ślad stosu będzie miał poprawną nazwę pliku i linię, a wystąpienie błędu będzie miało poprawną nazwę!

Dzieje się tak ze względu na użycie captureStackTracemetody, która tworzy stackwłaściwość na obiekcie docelowym (w tym przypadku jest CustomErrortworzona). Aby uzyskać więcej informacji o tym, jak to działa, sprawdź dokumentację tutaj .

Victor Schröder
źródło
1
this.message = this.message;czy to źle, czy są jeszcze szalone rzeczy, których nie wiem o JS?
Alex
1
Hej @Alex, masz całkowitą rację! Teraz jest naprawione. Dzięki!
Victor Schröder
18

Wysoko głosowana odpowiedź Crescent Fresh jest myląca. Chociaż jego ostrzeżenia są nieważne, istnieją inne ograniczenia, których nie rozwiązuje.

Po pierwsze, rozumowanie w akapicie „Ostrzeżeń:” Crescenta nie ma sensu. Wyjaśnienie to sugeruje, że kodowanie „paczki if (błąd MyError) else ...”) jest w pewien sposób uciążliwe lub pełne w porównaniu do wielu instrukcji catch. Wiele instancji instrukcji w jednym bloku catch jest tak samo zwięzłych jak wiele instrukcji catch - czysty i zwięzły kod bez żadnych sztuczek. Jest to świetny sposób na emulację doskonałej obsługi błędów specyficznych dla podtypów Java.

WRT „pojawia się właściwość komunikatu podklasy nie została ustawiona”, nie jest tak w przypadku użycia poprawnie skonstruowanej podklasy Error. Aby utworzyć własną podklasę ErrorX Error, po prostu skopiuj blok kodu zaczynający się od „var MyError =”, zmieniając jedno słowo „MyError” na „ErrorX”. (Jeśli chcesz dodać niestandardowe metody do swojej podklasy, postępuj zgodnie z przykładowym tekstem).

Rzeczywistym i znaczącym ograniczeniem podklasowania błędów JavaScript jest to, że w implementacjach JavaScript lub debuggerach, które śledzą i raportują śledzenie stosu i lokalizację wystąpienia, takie jak FireFox, lokalizacja we własnej implementacji podklasy Error zostanie zapisana jako punkt tworzenia wystąpienia klasy, natomiast jeśli użyjesz bezpośredniego błędu, będzie to miejsce, w którym uruchomiłeś „nowy błąd (...)”). Użytkownicy IE prawdopodobnie nigdy tego nie zauważą, ale użytkownicy Fire Bug na FF zobaczą bezużyteczne wartości nazw plików i numerów linii zgłaszane obok tych błędów i będą musieli przejść do szczegółowego śledzenia stosu do elementu nr 1, aby znaleźć rzeczywistą lokalizację wystąpienia.

Blaine
źródło
Czy mam rację - jeśli nie podklasujesz i nie używasz bezpośrednio nowego błędu (...), to nazwa pliku i linia są zgłaszane poprawnie? I w zasadzie mówisz, że w praktyce (prawdziwe i nie tylko o seksownym czy dekoracyjnym) Błędzie podklas, nie ma sensu?
jayarjo
6
Ta odpowiedź jest nieco myląca, ponieważ Crescent Fresh'szostała usunięta!
Peter
czy nadal tak jest? developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Numer linii 2 nie jest tam, gdzie nazwano nowy
Muhammad Umer
13

Co powiesz na to rozwiązanie?

Zamiast zgłaszać niestandardowy błąd za pomocą:

throw new MyError("Oops!");

Owinąłbyś obiekt Error (trochę jak dekorator):

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

Dzięki temu wszystkie atrybuty są poprawne, takie jak stos, nazwa_pliku nazwa_wiersza, itd.

Wystarczy, że skopiujesz atrybuty lub zdefiniujesz dla nich metody pobierające. Oto przykład użycia 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();
};
JoWie
źródło
1
Wydałem to rozwiązanie jako pakiet npm: npmjs.com/package/throwable
JoWie
Niesamowicie eleganckie rozwiązanie, dziękuję za udostępnienie! Jedna odmiana: new MyErr (arg1, arg2, new Error())w konstruktorze MyErr używamy Object.assigndo przypisania właściwości ostatniego argumentu dothis
Peeyush Kushwaha
Lubię to. Pomijasz ograniczenie, używając enkapsulacji zamiast dziedziczenia.
Jenny O'Reilly
13

Jak powiedzieli niektórzy, z ES6 jest dość łatwo:

class CustomError extends Error { }

Wypróbowałem to w mojej aplikacji (Angular, maszynopis) i po prostu nie działało. Po pewnym czasie odkryłem, że problem pochodzi z Typescript: O

Zobacz https://github.com/Microsoft/TypeScript/issues/13965

To bardzo niepokojące, ponieważ jeśli:

class CustomError extends Error {}


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

W węźle lub bezpośrednio w przeglądarce wyświetli: Custom error

Spróbuj uruchomić to z Typescript w swoim projekcie na placu zabaw Typescript, wyświetli się Basic error...

Rozwiązaniem jest wykonanie następujących czynności:

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
źródło
10

Moje rozwiązanie jest prostsze niż inne udzielone odpowiedzi i nie ma wad.

Zachowuje łańcuch prototypów Error i wszystkie właściwości w Error, bez potrzeby ich szczegółowej znajomości. Został przetestowany w Chrome, Firefox, Node i IE11.

Jedynym ograniczeniem jest dodatkowy wpis na górze stosu wywołań. Ale łatwo to zignorować.

Oto przykład z dwoma niestandardowymi parametrami:

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

Przykładowe użycie:

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
}

W środowiskach wymagających polyfil z setPrototypeOf:

Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
    obj.__proto__ = proto;
    return obj;
};
Ben
źródło
1
jak udokumentowano w mojej odpowiedzi, to rozwiązanie może powodować problem w przeglądarce Firefox lub innych przeglądarkach, które rejestrują tylko pierwszą linię śladu stosu w konsoli
Jonathan Benn
To jedyna odpowiedź, którą znalazłem, która działa dobrze z ES5 (używanie klas ES6 również działa dobrze). Błędy wyświetlają się w Chromium DevTools znacznie ładniej niż w przypadku innych odpowiedzi.
Ben Gubler,
Uwaga: jeśli używasz tego rozwiązania z TypeScript, musisz uruchomić throw CustomError('err')zamiastthrow new CustomError('err')
Ben Gubler
8

W powyższym przykładzie Error.apply(też Error.call) nic mi nie robi (Firefox 3.6 / Chrome 5). Obejście, którego używam to:

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';
panzi
źródło
5

W Node, jak powiedzieli inni, jest to proste:

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);
}
Muhammad Umer
źródło
3

Chcę tylko dodać do tego, co inni już stwierdzili:

Aby upewnić się, że niestandardowa klasa błędów wyświetla się poprawnie w śladzie stosu, należy ustawić właściwość name prototypu niestandardowej klasy błędów na właściwość name niestandardowej klasy błędów. To jest to co mam na mysli:

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

Pełny przykład to:

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

Kiedy wszystko jest już powiedziane i zrobione, rzucasz swój nowy wyjątek i wygląda to tak (leniwie wypróbowałem to w narzędziach programistycznych chrome):

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.
źródło
5
Czy to nie zastępuje właściwości name dla WSZYSTKICH instancji błędów?
panzi
@panzi masz rację. Naprawiłem mój mały błąd. Dzięki za heads-up!
Gautham C.
3

Moje 2 centy:

Dlaczego kolejna odpowiedź?

a) Ponieważ dostęp do Error.stack nieruchomości (jak w niektórych odpowiedziach) wiąże się z dużą utratą wydajności.

b) Ponieważ jest to tylko jedna linia.

c) Ponieważ rozwiązanie na https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error nie wydaje się zachowywać informacji o stosie.

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

przykład użycia

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

Co to robi?

this.__proto__.__proto__jest MyError.prototype.__proto__, więc ustawia __proto__WSZYSTKIE INSTANCJE MyError na określony nowo utworzony Błąd. Zachowuje właściwości i metody klasy MyError, a także umieszcza nowe właściwości Error (w tym .stack) w __proto__łańcuchu.

Oczywisty problem:

Nie można mieć więcej niż jednego wystąpienia MyError z przydatnymi informacjami o stosie.

Nie używaj tego rozwiązania, jeśli nie w pełni rozumiesz, co to this.__proto__.__proto__=robi.

Lucio M. Tato
źródło
2

Ponieważ wyjątki JavaScript są trudne do podklasowania, nie podklasuję. Właśnie tworzę nową klasę wyjątków i używam w niej błędu. Zmieniam właściwość Error.name, aby wyglądała jak mój niestandardowy wyjątek na konsoli:

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

Powyższy nowy wyjątek może zostać zgłoszony jak zwykły błąd i będzie działał zgodnie z oczekiwaniami, na przykład:

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

Uwaga: ślad stosu nie jest idealny, ponieważ doprowadzi cię do miejsca, w którym powstaje nowy błąd, a nie do miejsca, w którym rzucasz. Nie jest to wielka sprawa w Chrome, ponieważ zapewnia pełny ślad stosu bezpośrednio w konsoli. Ale na przykład jest to bardziej problematyczne w Firefoksie.

Jonathan Benn
źródło
Nie udaje się to w przypadkum = new InvalidInputError(); dontThrowMeYet(m);
Eric
@Eric Zgadzam się, ale wydaje się to dość małym ograniczeniem. Nigdy wcześniej nie musiałem tworzyć obiektu wyjątku (z wyjątkiem zastosowań metaprogramowania, takich jak mój przykładowy kod powyżej). Czy to naprawdę dla ciebie problem?
Jonathan Benn
Tak, zachowanie wydaje się takie samo, więc zmienię swoją odpowiedź. Nie jestem w 100% zadowolony ze śledzenia stosu, który prowadzi do wiersza „błąd błędu” w Firefox i Chrome
Jonathan Benn
1
@JathanathanBenn Jestem naprawdę spóźniony na imprezę, więc może już to odebrałeś. Często tworzę instancję obiektu wyjątku, gdy używam programowania asynchronicznego i obietnic. Następujące @ nazw Erica, często używam m = new ...wtedy Promise.reject(m). Nie jest to konieczne, ale kod jest łatwiejszy do odczytania.
BaldEagle,
1
@JathanathanBenn: (he on) 14 października, zdawało ci się, że utworzenie obiektu wyjątku przed jego rzuceniem byłoby rzadkością. Podałem przykład, kiedy to zrobiłem. Nie powiem, że to powszechne, ale dobrze jest mieć go, kiedy chcę. A mój kod jest bardziej czytelny, ponieważ tworzenie instancji odbywa się w jednej linii, a odrzucanie w drugiej. Mam nadzieję, że to wystarczy!
BaldEagle,
2

Jak wskazano w odpowiedzi Mohsena, w ES6 możliwe jest rozszerzanie błędów za pomocą klas. Jest to o wiele łatwiejsze, a ich zachowanie jest bardziej spójne z błędami natywnymi ... ale niestety nie jest to łatwe w użyciu w przeglądarce, jeśli potrzebujesz obsługi przeglądarek starszych niż ES6. Zobacz poniżej kilka uwag na temat tego, jak można to zaimplementować, ale tymczasem sugeruję stosunkowo proste podejście, które zawiera jedne z najlepszych sugestii z innych odpowiedzi:

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

W ES6 jest to tak proste, jak:

class CustomError extends Error {}

... i możesz wykryć obsługę klas ES6 za pomocą try {eval('class X{}'), ale dostaniesz błąd składniowy, jeśli spróbujesz dołączyć wersję ES6 do skryptu ładowanego przez starsze przeglądarki. Zatem jedynym sposobem obsługi wszystkich przeglądarek byłoby dynamiczne ładowanie osobnego skryptu (np. Przez AJAX lub eval()) dla przeglądarek obsługujących ES6. Kolejną komplikacją jest to, że eval()nie jest obsługiwana we wszystkich środowiskach (ze względu na zasady bezpieczeństwa treści), co może, ale nie musi, być rozważane dla twojego projektu.

Na razie więc pierwsze powyższe podejście lub po prostu Errorbezpośrednie użycie bez próby rozszerzenia wydaje się najlepsze, co można praktycznie zrobić dla kodu, który musi obsługiwać przeglądarki inne niż ES6.

Jest jeszcze jedno podejście, które niektórzy mogą rozważyć, polegające na użyciu, Object.setPrototypeOf()tam gdzie jest to możliwe, do utworzenia obiektu błędu, który jest instancją niestandardowego typu błędu, ale który wygląda i zachowuje się bardziej jak natywny błąd w konsoli (dzięki odpowiedzi Bena dla zalecenia). Oto moje podejście do tego podejścia: https://gist.github.com/mbrowne/fe45db61cea7858d11be933a998926a8 . Ale biorąc pod uwagę, że pewnego dnia będziemy mogli korzystać z ES6, osobiście nie jestem pewien, czy złożoność tego podejścia jest tego warta.

Matt Browne
źródło
1

Sposobem na to, aby to zrobić, jest zwrócenie wyniku zastosowania z konstruktora, a także ustawienie prototypu w zwykły skomplikowany sposób 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 ...>

Jedyne problemy z tym sposobem zrobienia tego w tym momencie (trochę to powtórzyłem) to:

  • właściwości inne niż stacki messagenie są uwzględnione w MyErrori
  • stacktrace ma dodatkową linię, która nie jest tak naprawdę konieczna.

Pierwszy problem można rozwiązać, wykonując iterację wszystkich niepoliczalnych właściwości błędu za pomocą sztuczki zawartej w tej odpowiedzi: Czy możliwe jest uzyskanie niepoliczalnej liczby dziedziczonych nazw właściwości obiektu? , ale nie jest to obsługiwane przez ie <9. Drugi problem można rozwiązać, odrywając tę ​​linię ze śladu stosu, ale nie jestem pewien, jak bezpiecznie to zrobić (może po prostu usuwając drugą linię e.stack.toString () ??).

BT
źródło
Zrobiłem moduł, który może rozszerzyć większość zwykłych starych obiektów javascript, w tym błędy. Jest dość dojrzały w tym momencie github.com/fresheneesz/proto
BT
1

Fragment pokazuje to wszystko.

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)
      }
    }
Amarpreet Singh
źródło
0

Zrobiłbym krok do tyłu i zastanowiłbym się, dlaczego chcesz to zrobić? Myślę, że chodzi o to, aby inaczej radzić sobie z różnymi błędami.

Na przykład w Pythonie możesz ograniczyć instrukcję catch tylko do catch MyValidationError, a być może chcesz zrobić coś podobnego w javascript.

catch (MyValidationError e) {
    ....
}

Nie możesz tego zrobić w javascript. Będzie tylko jeden blok catch. Powinieneś użyć instrukcji if w błędzie, aby określić jego typ.

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

Myślę, że zamiast tego rzuciłbym surowy obiekt o typie, komunikacie i dowolnych innych właściwościach, które uznasz za stosowne.

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

A kiedy złapiesz błąd:

catch(e) {
    if(e.type === "validation") {
         // handle error
    }
    // re-throw, or whatever else
}
hasen
źródło
1
Rzucanie przedmiotami nie jest świetnym pomysłem. Nie masz error.stack, standardowe narzędzia nie będą z nim współpracować itp. Lepszym sposobem byłoby dodanie właściwości do wystąpienia błędu, np.var e = new Error(); e.type = "validation"; ...
timruffles
0

Niestandardowy dekorator błędów

Jest to oparte na odpowiedzi George'a Baileya , ale rozszerza i upraszcza oryginalny pomysł. Jest napisany w CoffeeScript, ale łatwo go przekonwertować na JavaScript. Chodzi o to, aby rozszerzyć niestandardowy błąd Baileya za pomocą dekoratora, który go otacza, umożliwiając łatwe tworzenie nowych niestandardowych błędów.

Uwaga: Działa to tylko w wersji V8. W Error.captureStackTraceinnych środowiskach nie ma wsparcia .

Definiować

Dekorator przyjmuje nazwę typu błędu i zwraca funkcję, która pobiera komunikat o błędzie i dołącza nazwę błędu.

CoreError = (@message) ->

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

BaseError = (type) ->

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

Posługiwać się

Teraz można łatwo tworzyć nowe typy błędów.

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

Dla zabawy możesz teraz zdefiniować funkcję, która wyrzuca a SignatureError jeśli zostanie wywołana ze zbyt dużą liczbą argumentów.

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

Zostało to całkiem dobrze przetestowane i wydaje się działać idealnie na V8, utrzymując ślad, pozycję itp.

Uwaga: Używanie newjest opcjonalne podczas konstruowania niestandardowego błędu.

Carl Smith
źródło
0

jeśli nie zależy Ci na wynikach błędów, jest to najmniejsze co możesz zrobić

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

możesz go używać bez nowego tylko MyError (wiadomość)

Zmieniając prototyp po wywołaniu błędu konstruktora, nie musimy ustawiać stosu wywołań i komunikatu

bormat
źródło
0

Mohsen ma świetną odpowiedź powyżej w ES6, która określa nazwę, ale jeśli używasz TypeScript lub żyjesz w przyszłości, gdzie mam nadzieję, że ta propozycja dla pól klasy publicznej i prywatnej przeszła przez etap 3 jako propozycja i sprawiła, że do etapu 4 w ramach ECMAScript / JavaScript, możesz chcieć wiedzieć, że jest to tylko trochę krótsze. W etapie 3 przeglądarki zaczynają implementować funkcje, więc jeśli przeglądarka obsługuje tę funkcję, poniższy kod może po prostu działać. (Testowane w nowej przeglądarce Edge v81 wydaje się działać dobrze). Ostrzegamy jednak, że jest to obecnie funkcja niestabilna i należy z niej korzystać ostrożnie. Zawsze należy sprawdzać obsługę przeglądarki w przypadku funkcji niestabilnych. Ten post jest przeznaczony głównie dla przyszłych mieszkańców, gdy przeglądarki mogą to obsługiwać. Aby sprawdzić wsparcie, sprawdź MDNmogę użyć . Obecnie ma 66% wsparcia na rynku przeglądarek, co się tam pojawia, ale nie jest tak świetne, więc jeśli naprawdę chcesz go teraz użyć i nie chcesz czekać ani na transpilator, jak Babel, ani na coś takiego jak TypeScript .i

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

Porównaj to z bezimiennym błędem, który po rzuceniu nie zarejestruje swojej nazwy.

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

Jan
źródło