Qual è un buon modo per estendere Error in JavaScript?


369

Voglio inserire alcune cose nel mio codice JS e voglio che siano istanza di Errore, ma voglio anche che siano qualcos'altro.

In Python, in genere, una sottoclasse Eccezione.

Qual è la cosa appropriata da fare in JS?

Risposte:


218

L'unico campo standard con l'oggetto Error è la messageproprietà. (Vedi MDN , o Specifiche del linguaggio EcmaScript, sezione 15.11) Tutto il resto è specifico della piattaforma.

Gli ambienti Mosts impostano la stackproprietà, ma fileNameelineNumber sono praticamente inutili per essere utilizzato in eredità.

Quindi, l'approccio minimalista è:

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

Potresti sniffare lo stack, spostare elementi indesiderati da esso ed estrarre informazioni come fileName e lineNumber, ma per farlo sono necessarie informazioni sulla piattaforma su cui JavaScript è attualmente in esecuzione. La maggior parte dei casi non è necessaria e puoi farlo in post mortem se vuoi davvero.

Safari è un'eccezione notevole. Non esiste alcuna stackproprietà, ma i throwset di parole chiave sourceURLe le lineproprietà dell'oggetto che viene lanciato. Queste cose sono garantite per essere corrette.

I casi di test che ho usato possono essere trovati qui: Confronto oggetti di errore fatto da sé JavaScript .


19
È possibile spostare l' this.name = 'MyError' esterno della funzione e modificarlo in MyError.prototype.name = 'MyError'.
Daniel Beardsley,

36
Questa è l'unica risposta corretta qui, anche se per motivi di stile, probabilmente la scriverei così. function MyError(message) { this.message = message; this.stack = Error().stack; } MyError.prototype = Object.create(Error.prototype); MyError.prototype.name = "MyError";
kybernetikos,

11
Vorrei aggiungere MyError.prototype.constructor = MyErroranche io .
Bharat Khatri,

3
in ES6 Error.call (questo, messaggio); dovrebbe inizializzare this, giusto?
4esn0k,

4
MyError.prototype = Object.create (Error.prototype);
Robin piace l'uccello il

170

In ES6:

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

fonte


53
Vale la pena ricordare che questo non funziona se si utilizzano le funzionalità ES6 tramite un transpiler, come Babel, poiché le sottoclassi devono estendere una classe.
aaaidan,

6
Se stai usando babel e sei sul nodo> 5.x non dovresti usare il predefinito es2015 ma npmjs.com/package/babel-preset-node5 ti permetterebbe di usare estensioni es6 native più altro
Ace

2
Questo è il modo migliore per farlo quando possibile. Gli errori personalizzati si comportano più come errori regolari sia in Chrome che in Firefox (e probabilmente anche in altri browser).
Matt Browne,

2
Per i browser, notare che è possibile rilevare il supporto per le classi in fase di esecuzione e di conseguenza ricorrere a una versione non di classe. Codice di rilevamento:var supportsClasses = false; try {eval('class X{}'); supportsClasses = true;} catch (e) {}
Matt Browne,

31
Per facilità di manutenzione , utilizzare this.name = this.constructor.name;invece.
Константин Ван,

53

In breve:

  • Se si utilizza ES6 senza transpiler :

    class CustomError extends Error { /* ... */}
  • Se stai usando il transpiler Babel :

Opzione 1: usa babel-plugin-transform-builtin-extension

Opzione 2: fai da te (ispirato alla stessa libreria)

    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);
  • Se si utilizza ES5 puro :

    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;
    }
  • Alternativa: utilizzare la struttura Classtrophobic

Spiegazione:

Perché l'estensione della classe Error tramite ES6 e Babel è un problema?

Perché un'istanza di CustomError non è più riconosciuta come tale.

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

Infatti, dalla documentazione ufficiale di Babele, si può non estendersi alcun built-in classi JavaScript come Date, Array, DOMo Error.

Il problema è descritto qui:

E le altre risposte SO?

Tutte le risposte fornite risolvono il instanceofproblema ma perdi l'errore normale console.log:

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

Considerando che usando il metodo sopra menzionato, non solo risolvi il instanceofproblema ma mantieni anche l'errore regolare console.log:

console.log(new CustomError('test'));
// output:
// Error: test
//     at CustomError (<anonymous>:2:32)
//     at <anonymous>:1:5

1
class CustomError extends Error { /* ... */}non gestisce correttamente argomenti specifici del fornitore ( lineNumber, ecc.), "Estensione dell'errore in Javascript con sintassi ES6" è specifica di Babel, la soluzione ES5 utilizza conste non gestisce argomenti personalizzati.
Indolente il

Risposta molto completa.
Daniele Orlando,

Questo in realtà fornisce la soluzione più completa e spiega perché i vari pezzi sono necessari. Grazie mille JBE!
AndrewSouthpaw,

Questo mi ha aiutato a risolvere il problema ereditando da "Errore". È stato un incubo di due ore!
Iulian Pinzaru,

2
Vale la pena notare che il problema con console.log(new CustomError('test') instanceof CustomError);// falseera vero al momento della scrittura, ma ora è stato risolto. In effetti il problema collegato nella risposta è stato risolto e possiamo testare il comportamento corretto qui e incollando il codice nel REPL e vedendo come viene correttamente traspilato per creare un'istanza con la catena di prototipi corretta.
Nobita

44

Modifica: leggi i commenti. Si scopre che questo funziona bene solo in V8 (Chrome / Node.JS). Il mio intento era di fornire una soluzione cross-browser, che avrebbe funzionato in tutti i browser e fornire la traccia dello stack dove c'è supporto.

Modifica: ho creato questo Wiki della community per consentire ulteriori modifiche.

Soluzione per V8 (Chrome / Node.JS), funziona in Firefox e può essere modificata per funzionare principalmente correttamente in IE. (vedi fine del post)

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
}

Post originale su "Mostrami il codice!"

Versione breve:

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

Continuo this.constructor.prototype.__proto__ = Error.prototypeall'interno della funzione per mantenere tutto il codice insieme. Ma puoi anche sostituirlo this.constructorcon UserErrore ciò ti consente di spostare il codice all'esterno della funzione, quindi viene chiamato solo una volta.

Se segui quel percorso, assicurati di chiamare quella linea prima della prima volta che lanci UserError.

Questo avvertimento non applica la funzione, poiché le funzioni vengono create per prime, indipendentemente dall'ordine. Pertanto, è possibile spostare la funzione alla fine del file, senza problemi.

Compatibilità del browser

Funziona con Firefox e Chrome (e Node.JS) e riempie tutte le promesse.

Internet Explorer non riesce nel modo seguente

  • Gli errori non devono err.stackiniziare, quindi "non è colpa mia".

  • Error.captureStackTrace(this, this.constructor) non esiste, quindi è necessario fare qualcos'altro come

    if(Error.captureStackTrace) // AKA if not IE
        Error.captureStackTrace(this, this.constructor)
  • toStringcessa di esistere quando si esegue una sottoclasse Error. Quindi devi anche aggiungere.

    else
        this.toString = function () { return this.name + ': ' + this.message }
  • IE non considererà UserErrorun a instanceof Errormeno che tu non esegua il seguente tempo prima di tethrow UserError

    UserError.prototype = Error.prototype

16
Non penso che Firefox abbia effettivamente captureStackTrace. È un'estensione V8 e per me non è definito in Firefox, né posso trovare alcun riferimento sul web a Firefox che lo supporti. (Grazie comunque!)
Geoff il

5
Error.call(this)non sta effettivamente facendo nulla poiché restituisce un errore anziché modificarlo this.
kybernetikos,

1
Funziona benissimo per Node.js
Rudolf Meijering,

1
UserError.prototype = Error.prototypeè fuorviante. Questo non fa eredità, questo li rende la stessa classe .
Halcyon,

1
Credo che Object.setPrototypeOf(this.constructor.prototype, Error.prototype)sia preferito this.constructor.prototype.__proto__ = Error.prototype, almeno per i browser attuali.
ChrisV,

29

Per evitare la caldaia per ogni diverso tipo di errore, ho combinato la saggezza di alcune soluzioni in una  createErrorTypefunzione:

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

Quindi è possibile definire facilmente nuovi tipi di errore come segue:

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

C'è un motivo per cui hai ancora bisogno della linea this.name = name;?
Peter Tseng

@PeterTseng Poiché nameè già impostato sul prototipo, non è più necessario. L'ho rimosso. Grazie!
Ruben Verborgh,

27

Nel 2018 , penso che questo sia il modo migliore; che supporta IE9 + e browser moderni.

AGGIORNAMENTO : vedere questo test e repository per il confronto su diverse implementazioni.

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

Attenzione anche che la __proto__proprietà è obsoleta, ampiamente utilizzata in altre risposte.


1
Perché stai usando setPrototypeOf()? Almeno secondo MDN, è generalmente sconsigliato usarlo se puoi ottenere la stessa cosa semplicemente impostando la .prototypeproprietà sul costruttore (come stai facendo nel elseblocco per i browser che non hanno setPrototypeOf).
Matt Browne,

Cambiare il prototipo di un oggetto è scoraggiato tutti insieme, no setPrototypeOf. Ma se ne hai ancora bisogno (come richiesto da OP), dovresti usare la metodologia integrata. Come indica MDN, questo è considerato il modo corretto di impostare il prototipo di un oggetto. In altre parole, MDN afferma di non modificare il prototipo (poiché influisce sulle prestazioni e sull'ottimizzazione) ma, se necessario, utilizzare setPrototypeOf.
Onur Yıldırım,

Il mio punto era che non penso che tu debba effettivamente cambiare il prototipo qui. Potresti semplicemente usare la tua linea in fondo ( CustomError.prototype = Object.create(Error.prototype)). Inoltre, Object.setPrototypeOf(CustomError, Error.prototype)sta impostando il prototipo del costruttore stesso anziché specificare il prototipo per nuove istanze di CustomError. Ad ogni modo, nel 2016 penso che ci sia davvero un modo migliore per estendere gli errori, anche se sto ancora cercando di capire come usarlo insieme a Babel: github.com/loganfsmyth/babel-plugin-transform-builtin-extend/…
Matt Browne

CustomError.prototype = Object.create(Error.prototype)sta anche cambiando il prototipo. È necessario modificarlo poiché non esiste alcuna logica di estensione / ereditari integrata in ES5. Sono sicuro che il plugin babel di cui parli fa cose simili.
Onur Yıldırım,

1
Ho creato una sintesi che dimostra perché l'utilizzo Object.setPrototypeOfnon ha senso qui, almeno non nel modo in cui lo stai usando: gist.github.com/mbrowne/4af54767dcb3d529648f5a8aa11d6348 . Forse intendevi scrivere Object.setPrototypeOf(CustomError.prototype, Error.prototype)- avrebbe un po 'più senso (anche se non offre alcun vantaggio rispetto alla semplice impostazione CustomError.prototype).
Matt Browne,

19

Per completezza - solo perché nessuna delle risposte precedenti ha menzionato questo metodo - se stai lavorando con Node.js e non devi preoccuparti della compatibilità del browser, l'effetto desiderato è abbastanza facile da ottenere con il inheritsdel utilmodulo ( documenti ufficiali qui ).

Ad esempio, supponiamo che tu voglia creare una classe di errore personalizzata che accetta un codice di errore come primo argomento e il messaggio di errore come secondo argomento:

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

Ora puoi creare un'istanza e passare / lanciare 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!');

Nota che, con questo frammento, la traccia dello stack avrà il nome e la riga del file corretti e l'istanza di errore avrà il nome corretto!

Ciò accade a causa dell'utilizzo del captureStackTracemetodo, che crea una stackproprietà sull'oggetto target (in questo caso, l' CustomErroristanza). Per maggiori dettagli su come funziona, consulta la documentazione qui .


1
this.message = this.message;è sbagliato o ci sono ancora cose folli che non so su JS?
Alex,

1
Ehi @Alex, hai perfettamente ragione! Ora è riparato. Grazie!
Victor Schröder,

18

La risposta di Crescent Fresh, altamente votata, è fuorviante. Sebbene i suoi avvertimenti non siano validi, ci sono altre limitazioni che non affronta.

In primo luogo, il ragionamento nel paragrafo "Caveats:" di Crescent non ha senso. La spiegazione implica che la codifica "un mucchio di if (errore di MyError) altro ..." è in qualche modo onerosa o dettagliata rispetto a più dichiarazioni catch. Più istruzioni di istanza in un singolo blocco catch sono altrettanto concise di più istruzioni catch: codice pulito e conciso senza trucchi. Questo è un ottimo modo per emulare l'eccezionale gestione degli errori specifici del sottotipo gettabile di Java.

WRT "appare che la proprietà del messaggio della sottoclasse non viene impostata", non è così se si utilizza una sottoclasse di errori correttamente costruita. Per creare la tua sottoclasse ErrorX Error, copia il blocco di codice che inizia con "var MyError =", cambiando la parola "MyError" in "ErrorX". (Se si desidera aggiungere metodi personalizzati alla sottoclasse, seguire il testo di esempio).

Il limite reale e significativo della sottoclasse degli errori JavaScript è che per le implementazioni JavaScript o i debugger che tracciano e riportano sulla traccia dello stack e sulla posizione dell'istanza, come FireFox, una posizione nella propria implementazione della sottoclasse Errore verrà registrata come punto di istanza della classe, mentre se si utilizzava un errore diretto, sarebbe la posizione in cui è stato eseguito "nuovo errore (...)"). Gli utenti di IE probabilmente non se ne accorgerebbero mai, ma gli utenti di Fire Bug su FF vedranno i nomi inutili di nome file e numero di riga riportati accanto a questi errori e dovranno eseguire il drill-down sulla traccia dello stack sull'elemento n. 1 per trovare la reale posizione dell'istanza.


Ho capito bene: se non esegui la sottoclasse e utilizzi direttamente il nuovo errore (...), il nome e la riga del file vengono segnalati correttamente? E fondamentalmente dici che sulla pratica (reale e non solo di tipo sexy o decorativo) Errori di sottoclasse, non ha senso?
jayarjo,

6
Questa risposta è un po 'confusa in quanto Crescent Fresh'sè stata eliminata!
Peter,

è ancora così? developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Il numero di riga è 2 non dove è stato chiamato il nuovo
Muhammad Umer,

13

Che ne dici di questa soluzione?

Invece di lanciare il tuo errore personalizzato usando:

throw new MyError("Oops!");

Dovresti avvolgere l'oggetto Error (un po 'come un decoratore):

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

Questo assicura che tutti gli attributi siano corretti, come stack, fileName lineNumber, eccetera.

Tutto ciò che devi fare è copiare gli attributi o definire i getter per essi. Ecco un esempio usando getter (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
Ho rilasciato questa soluzione come pacchetto npm
JoWie

Soluzione incredibilmente elegante, grazie per la condivisione! Una variante: new MyErr (arg1, arg2, new Error())e nel costruttore MyErr usiamo Object.assignassegnare le proprietà dell'ultimo arg athis
Peeyush Kushwaha

Mi piace questo. Si elude una limitazione utilizzando l'incapsulamento anziché l'ereditarietà.
Jenny O'Reilly,

13

Come alcune persone hanno detto, è abbastanza facile con ES6:

class CustomError extends Error { }

Quindi l'ho provato nella mia app (angolare, dattiloscritto) e non ha funzionato. Dopo qualche tempo ho scoperto che il problema proviene da Typescript: O

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

È molto inquietante perché se lo fai:

class CustomError extends Error {}


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

Nel nodo o direttamente nel browser verrà visualizzato: Custom error

Prova a eseguirlo con Typescript nel tuo progetto nel parco giochi Typescript, verrà visualizzato Basic error...

La soluzione è fare quanto segue:

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

La mia soluzione è più semplice delle altre risposte fornite e non ha i lati negativi.

Conserva la catena di prototipi Error e tutte le proprietà su Error senza averne bisogno. È stato testato in Chrome, Firefox, Node e IE11.

L'unica limitazione è una voce aggiuntiva nella parte superiore dello stack di chiamate. Ma questo è facilmente ignorato.

Ecco un esempio con due parametri personalizzati:

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

Esempio di utilizzo:

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
}

Per ambienti che richiedono un polyfil di setPrototypeOf:

Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
    obj.__proto__ = proto;
    return obj;
};

1
come documentato nella mia risposta, questa soluzione può causare un problema in Firefox o in altri browser che registrano solo la prima riga della traccia dello stack nella console
Jonathan Benn,

Questa è l'unica risposta che ho trovato che funziona bene con ES5 (anche l'uso delle classi ES6 funziona bene). Gli errori vengono visualizzati molto più bene in Chromium DevTools rispetto ad altre risposte.
Ben Gubler,

Nota: se si utilizza questa soluzione con TypeScript, è necessario eseguire throw CustomError('err')invece dithrow new CustomError('err')
Ben Gubler il

8

Nell'esempio sopra Error.apply(anche Error.call) non fa nulla per me (Firefox 3.6 / Chrome 5). Una soluzione alternativa che utilizzo è:

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

In Node come altri hanno già detto, è semplice:

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

Voglio solo aggiungere ciò che altri hanno già affermato:

Per assicurarsi che la classe di errore personalizzata venga visualizzata correttamente nella traccia dello stack, è necessario impostare la proprietà name del prototipo della classe di errore personalizzata sulla proprietà name della classe di errore personalizzata. Questo è ciò che intendo:

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

Quindi l'esempio completo sarebbe:

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

Quando tutto è stato detto e fatto, lanci la tua nuova eccezione e sembra così (l'ho provato pigramente negli strumenti di sviluppo di 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)

5
Questo non sovrascrive la proprietà name per TUTTE le istanze di errore?
Panzi,

@panzi hai ragione. Ho corretto il mio piccolo bug. Grazie per il testa a testa!
Gautham C.,

3

I miei 2 centesimi:

Perché un'altra risposta?

a) Perché l'accesso alla Error.stackproprietà (come in alcune risposte) comporta una forte penalità per le prestazioni.

b) Perché è solo una riga.

c) Perché la soluzione su https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error non sembra preservare le informazioni sullo stack.

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

esempio di utilizzo

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

Che cosa fa?

this.__proto__.__proto__è MyError.prototype.__proto__, quindi sta impostando __proto__FOR ALL INSTANCE di MyError su uno specifico errore appena creato. Mantiene proprietà e metodi della classe MyError e mette anche le nuove proprietà Error (incluso .stack) nella __proto__catena.

Problema evidente:

Non puoi avere più di un'istanza di MyError con utili informazioni sullo stack.

Non utilizzare questa soluzione se non si comprende completamente cosa this.__proto__.__proto__=fa.


2

Dal momento che le eccezioni JavaScript sono difficili da classificare, non lo faccio. Ho appena creato una nuova classe di eccezione e utilizzo un errore al suo interno. Cambio la proprietà Error.name in modo che appaia la mia eccezione personalizzata sulla console:

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

La nuova eccezione sopra può essere generata come un normale errore e funzionerà come previsto, ad esempio:

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

Avvertenza: la traccia dello stack non è perfetta, poiché ti porterà dove viene creato il nuovo errore e non dove si lancia. Questo non è un grosso problema su Chrome perché ti fornisce una traccia dello stack completo direttamente nella console. Ma è più problematico su Firefox, per esempio.


Questo fallisce nel casom = new InvalidInputError(); dontThrowMeYet(m);
Eric

@Eric sono d'accordo, ma questa sembra una limitazione piuttosto piccola. Non ho mai avuto bisogno di creare un'istanza di un oggetto eccezione in anticipo (tranne che per gli usi di meta-programmazione come il mio esempio di codice sopra). È davvero un problema per te?
Jonathan Benn,

Sì, il comportamento sembra essere lo stesso, quindi cambierò la mia risposta. Non sono soddisfatto al 100% della traccia dello stack, che ti porta alla riga "var error" su Firefox e Chrome
Jonathan Benn,

1
@JonathanBenn Sono in ritardo alla festa, quindi forse l'hai già raccolto. Spesso istanza un oggetto eccezione quando utilizzo la programmazione asincrona e Promesse. Seguendo i nomi di @ Eric, uso spesso m = new ...allora Promise.reject(m). Non è necessario, ma il codice è più facile da leggere.
BaldEagle,

1
@JonathanBenn: (lui) il 14 ottobre, sembravi pensare di creare un'istanza di un oggetto eccezione prima di lanciarlo sarebbe raro. Ho fatto un esempio di una volta che lo faccio. Non dirò che è comune, ma è utile averlo quando lo voglio. E il mio codice è più leggibile perché l'istanza è tutta su una riga e il rifiuto di tutto su un'altra. Spero che lo faccia!
BaldEagle,

2

Come sottolineato nella risposta di Mohsen, in ES6 è possibile estendere gli errori usando le classi. È molto più semplice e il loro comportamento è più coerente con gli errori nativi ... ma sfortunatamente non è semplice usarlo nel browser se è necessario supportare i browser pre-ES6. Vedi sotto per alcune note su come potrebbe essere implementato, ma nel frattempo suggerisco un approccio relativamente semplice che incorpori alcuni dei migliori suggerimenti da altre risposte:

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

In ES6 è semplice come:

class CustomError extends Error {}

... e puoi rilevare il supporto per le classi ES6 con try {eval('class X{}') , ma otterrai un errore di sintassi se tenti di includere la versione ES6 in uno script caricato da browser meno recenti. Quindi l'unico modo per supportare tutti i browser sarebbe caricare uno script separato in modo dinamico (ad es. Tramite AJAX o eval()) per i browser che supportano ES6. Un'ulteriore complicazione è che eval()non è supportato in tutti gli ambienti (a causa delle politiche di sicurezza dei contenuti), che può essere o meno una considerazione per il tuo progetto.

Quindi, per ora, o il primo approccio sopra o semplicemente usando Error direttamente senza tentare di estenderlo sembra essere il migliore che può essere praticamente fatto per il codice che deve supportare browser non ES6.

C'è un altro approccio che alcune persone potrebbero voler considerare, che è quello di utilizzare Object.setPrototypeOf()dove disponibile per creare un oggetto di errore che sia un'istanza del tipo di errore personalizzato ma che assomigli e si comporti più come un errore nativo nella console (grazie alla risposta di Ben per la raccomandazione). Ecco la mia opinione su questo approccio: https://gist.github.com/mbrowne/fe45db61cea7858d11be933a998926a8 . Ma dato che un giorno saremo in grado di usare solo ES6, personalmente non sono sicuro che la complessità di questo approccio ne valga la pena.


1

Il modo per farlo nel modo giusto è quello di restituire il risultato dell'applicazione dal costruttore, oltre a impostare il prototipo nel solito complicato modo 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 ...>

Gli unici problemi con questo modo di farlo a questo punto (l'ho ripetuto un po ') sono quelli

  • proprietà diverse da stacke messagenon sono incluse in MyErrore
  • lo stacktrace ha una riga aggiuntiva che non è davvero necessaria.

Il primo problema potrebbe essere risolto ripetendo tutte le proprietà non enumerabili dell'errore usando il trucco in questa risposta: è possibile ottenere i nomi delle proprietà ereditate non enumerabili di un oggetto?, ma questo non è supportato da ie <9. Il secondo problema potrebbe essere risolto strappando quella riga nella traccia dello stack, ma non sono sicuro di come farlo in modo sicuro (forse semplicemente rimuovendo la seconda riga di e.stack.toString () ??).


Ho creato un modulo in grado di estendere il più vecchio oggetto javascript normale, inclusi errori. È abbastanza maturo a questo punto github.com/fresheneesz/proto
BT

1

Lo snippet mostra tutto.

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

Vorrei fare un passo indietro e considerare il motivo per cui vuoi farlo? Penso che il punto sia trattare diversi errori in modo diverso.

Ad esempio, in Python, puoi limitare l'istruzione catch a solo cattura MyValidationErrore forse vuoi essere in grado di fare qualcosa di simile in javascript.

catch (MyValidationError e) {
    ....
}

Non puoi farlo in javascript. Ci sarà solo un blocco di cattura. Dovresti usare un'istruzione if sull'errore per determinarne il tipo.

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

Penso che invece lancerei un oggetto grezzo con un tipo, un messaggio e qualsiasi altra proprietà che ritieni appropriata.

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

E quando si rileva l'errore:

catch(e) {
    if(e.type === "validation") {
         // handle error
    }
    // re-throw, or whatever else
}

1
Lanciare un oggetto non è una grande idea. Non avete error.stack, utensili standard non funziona con esso, ecc ecc Un modo migliore sarebbe quello di aggiungere proprietà a un'istanza errore, ad esempiovar e = new Error(); e.type = "validation"; ...
timruffles

0

Decoratore errori personalizzato

Questo si basa sulla risposta di George Bailey , ma estende e semplifica l'idea originale. È scritto in CoffeeScript, ma è facile da convertire in JavaScript. L'idea è di estendere l'errore personalizzato di Bailey con un decoratore che lo avvolge, permettendoti di creare facilmente nuovi errori personalizzati.

Nota: funzionerà solo con V8. Non c'è supporto per Error.captureStackTracein altri ambienti.

Definire

Il decoratore prende un nome per il tipo di errore e restituisce una funzione che accetta un messaggio di errore e racchiude il nome dell'errore.

CoreError = (@message) ->

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

BaseError = (type) ->

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

Uso

Ora è semplice creare nuovi tipi di errore.

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

Per divertimento, ora potresti definire una funzione che genera un SignatureErrorse viene chiamato con troppi argomenti.

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

Questo è stato testato abbastanza bene e sembra funzionare perfettamente su V8, mantenendo il traceback, la posizione ecc.

Nota: l'utilizzo newè facoltativo quando si crea un errore personalizzato.


0

se non ti interessano le prestazioni per errori, questo è il minimo che puoi fare

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

puoi usarlo senza nuovo solo MyError (messaggio)

Modificando il prototipo dopo la chiamata al costruttore Error non è necessario impostare il callstack e il messaggio


0

Mohsen ha una grande risposta sopra in ES6 che imposta il nome, ma se stai usando TypeScript o se stai vivendo in futuro dove si spera che questa proposta per campi di classe pubblici e privati sia passata oltre la fase 3 come proposta e ce l'abbia fatta nella fase 4 come parte di ECMAScript / JavaScript, allora potresti voler sapere che questo è solo un po 'più breve. La fase 3 è quella in cui i browser iniziano a implementare le funzionalità, quindi se il tuo browser lo supporta il codice qui sotto potrebbe funzionare. (Testato nel nuovo browser Edge v81 sembra funzionare bene). Tieni presente che al momento questa è una funzione instabile e deve essere utilizzata con cautela e dovresti sempre controllare il supporto del browser sulle funzionalità instabili. Questo post è principalmente per quei futuri abitanti quando i browser potrebbero supportarlo. Per verificare il supporto, controllare MDNe posso usare . Attualmente ha il 66% di supporto in tutto il mercato dei browser che sta arrivando ma non è eccezionale, quindi se vuoi davvero usarlo ora e non vuoi aspettare o usa un transpiler come Babel o qualcosa di simile a TypeScript .

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

Confronta questo con un errore senza nome che, se lanciato, non registra il suo nome.

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.