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?
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:
L'unico campo standard con l'oggetto Error è la message
proprietà. (Vedi MDN , o Specifiche del linguaggio EcmaScript, sezione 15.11) Tutto il resto è specifico della piattaforma.
Gli ambienti Mosts impostano la stack
proprietà, ma fileName
elineNumber
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 stack
proprietà, ma i throw
set di parole chiave sourceURL
e le line
proprietà 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 .
function MyError(message) { this.message = message; this.stack = Error().stack; } MyError.prototype = Object.create(Error.prototype); MyError.prototype.name = "MyError";
MyError.prototype.constructor = MyError
anche io .
this
, giusto?
In ES6:
class MyError extends Error {
constructor(message) {
super(message);
this.name = 'MyError';
}
}
var supportsClasses = false; try {eval('class X{}'); supportsClasses = true;} catch (e) {}
this.name = this.constructor.name;
invece.
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
, DOM
o Error
.
Il problema è descritto qui:
E le altre risposte SO?
Tutte le risposte fornite risolvono il instanceof
problema 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 instanceof
problema 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
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 const
e non gestisce argomenti personalizzati.
console.log(new CustomError('test') instanceof CustomError);// false
era 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.
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.prototype
all'interno della funzione per mantenere tutto il codice insieme. Ma puoi anche sostituirlo this.constructor
con UserError
e 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.stack
iniziare, 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)
toString
cessa di esistere quando si esegue una sottoclasse Error
. Quindi devi anche aggiungere.
else
this.toString = function () { return this.name + ': ' + this.message }
IE non considererà UserError
un a instanceof Error
meno che tu non esegua il seguente tempo prima di tethrow UserError
UserError.prototype = Error.prototype
Error.call(this)
non sta effettivamente facendo nulla poiché restituisce un errore anziché modificarlo this
.
UserError.prototype = Error.prototype
è fuorviante. Questo non fa eredità, questo li rende la stessa classe .
Object.setPrototypeOf(this.constructor.prototype, Error.prototype)
sia preferito this.constructor.prototype.__proto__ = Error.prototype
, almeno per i browser attuali.
Per evitare la caldaia per ogni diverso tipo di errore, ho combinato la saggezza di alcune soluzioni in una createErrorType
funzione:
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';
});
this.name = name;
?
name
è già impostato sul prototipo, non è più necessario. L'ho rimosso. Grazie!
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.
setPrototypeOf()
? Almeno secondo MDN, è generalmente sconsigliato usarlo se puoi ottenere la stessa cosa semplicemente impostando la .prototype
proprietà sul costruttore (come stai facendo nel else
blocco per i browser che non hanno setPrototypeOf
).
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
.
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/…
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.
Object.setPrototypeOf
non 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
).
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 inherits
del util
modulo ( 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 captureStackTrace
metodo, che crea una stack
proprietà sull'oggetto target (in questo caso, l' CustomError
istanza). Per maggiori dettagli su come funziona, consulta la documentazione qui .
this.message = this.message;
è sbagliato o ci sono ancora cose folli che non so su JS?
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.
Crescent Fresh's
è stata eliminata!
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();
};
new MyErr (arg1, arg2, new Error())
e nel costruttore MyErr usiamo Object.assign
assegnare le proprietà dell'ultimo arg athis
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;
}
}
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;
};
throw CustomError('err')
invece dithrow new CustomError('err')
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';
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);
}
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)
I miei 2 centesimi:
a) Perché l'accesso alla Error.stack
proprietà (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/
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.
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.
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.
m = new InvalidInputError(); dontThrowMeYet(m);
m = new ...
allora Promise.reject(m)
. Non è necessario, ma il codice è più facile da leggere.
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.
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
stack
e message
non sono incluse in MyError
eIl 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 () ??).
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)
}
}
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 MyValidationError
e 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
}
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"; ...
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.captureStackTrace
in altri ambienti.
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 }"
Ora è semplice creare nuovi tipi di errore.
StorageError = BaseError "Storage"
SignatureError = BaseError "Signature"
Per divertimento, ora potresti definire una funzione che genera un SignatureError
se 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.
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
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");
this.name = 'MyError'
esterno della funzione e modificarlo inMyError.prototype.name = 'MyError'
.