Quando sviluppiamo qualcosa, spesso abbiamo bisogno delle nostre classi di errore per riflettere cose specifiche che potrebbero andare male nei nostri compiti. Per errori nelle operazioni di rete potremmo aver bisogno di HttpError
, per operazioni di database DbError
, per operazioni di ricerca NotFoundError
e così via.
I nostri errori dovrebbero supportare le proprietà di errore di base comemessage
,name
e, preferibilmente,stack
. Ma possono anche avere altre proprietà proprie, ad esempio, HttpError
gli oggetti possono avere una proprietàstatusCode
con un valore come404
o403
o500
.
JavaScript consente di utilizzare throw
con qualsiasi argomento, quindi tecnicamente le nostre classi di errore personalizzate non devono ereditare daError
. Ma se ereditiamo, diventa possibile utilizzareobj instanceof Error
per identificare gli oggetti di errore. Quindi è meglio ereditare da esso.
Man mano che l’applicazione cresce, i nostri errori formano naturalmente una gerarchia., Ad esempio, HttpTimeoutError
può ereditare da HttpError
e così via.
Errore di estensione
Ad esempio, consideriamo una funzione readUser(json)
che dovrebbe leggere JSON con i dati dell’utente.
Ecco un esempio di come potrebbe apparire un json
valido:
Internamente, useremo JSON.parse
. Se riceve malformato json
, allora lancia SyntaxError
., Ma anche se json
è sintatticamente corretto, ciò non significa che sia un utente valido, giusto? Potrebbe mancare i dati necessari. Ad esempio, potrebbe non avere proprietà name
e age
essenziali per i nostri utenti.
La nostra funzione readUser(json)
non solo legge JSON, ma controlla (“convalida”) i dati. Se non ci sono campi obbligatori o il formato è errato, allora si tratta di un errore. E questo non è un SyntaxError
, perché i dati sono sintatticamente corretti, ma un altro tipo di errore., Lo chiameremo ValidationError
e creeremo una classe per questo. Un errore di questo tipo dovrebbe anche portare le informazioni sul campo incriminato.
La nostra classe ValidationError
dovrebbe ereditare dalla classe Error
incorporata.
Quella classe è integrata, ma ecco il suo codice approssimativo in modo da poter capire cosa stiamo estendendo:
Ora ereditiamoValidationError
da esso e proviamolo in azione:
Nota: nella riga(1)
chiamiamo il costruttore genitore., JavaScript ci richiede di chiamare super
nel costruttore figlio, quindi è obbligatorio. Il costruttore padre imposta la proprietàmessage
.
Il costruttore genitore imposta anche la proprietà name
su "Error"
, quindi nella riga (2)
lo ripristiniamo al valore giusto.,
Proviamo ad usarlo inreadUser(json)
:
Il bloccotry..catch
nel codice sopra gestisce sia il nostroValidationError
che il built-inSyntaxError
daJSON.parse
.
Si prega di dare un’occhiata a come usiamoinstanceof
per verificare il tipo di errore specifico nella riga(*)
.,
Si potrebbe anche guardare err.name
in questo modo:
// ...// instead of (err instanceof SyntaxError)} else if (err.name == "SyntaxError") { // (*)// ...
instanceof
versione è molto meglio, perché, in futuro, abbiamo intenzione di estendere ValidationError
fare sottotipi, come il PropertyRequiredError
. Einstanceof
il controllo continuerà a funzionare per le nuove classi ereditarie. Quindi è a prova di futuro.
Inoltre è importante che secatch
incontra un errore sconosciuto, allora lo ripassa nella riga(**)
., Il bloccocatch
sa solo come gestire errori di convalida e sintassi, altri tipi (a causa di un errore di battitura nel codice o altri sconosciuti) dovrebbero cadere.
Ulteriore ereditarietà
La classeValidationError
è molto generica. Molte cose possono andare male. La proprietà potrebbe essere assente o potrebbe essere in un formato errato (come un valore stringa per age
). Facciamo una classe più concreta PropertyRequiredError
, esattamente per le proprietà assenti. Porterà ulteriori informazioni sulla proprietà che manca.,
La nuova classe PropertyRequiredError
è facile da usare: abbiamo solo bisogno di passare il nome della proprietà: new PropertyRequiredError(property)
. Il message
leggibile dall’uomo viene generato dal costruttore.
Si prega di notare che this.name
in PropertyRequiredError
costruttore viene nuovamente assegnato manualmente. Potrebbe diventare un po ‘ noioso – assegnare this.name = <class name>
in ogni classe di errore personalizzata. Possiamo evitarlo creando la nostra classe” basic error “che assegna this.name = this.constructor.name
. E poi ereditare tutti i nostri errori personalizzati da esso.,
Chiamiamolo MyError
.
Ecco il codice conMyError
e altre classi di errore personalizzate, semplificate:
Ora gli errori personalizzati sono molto più brevi, in particolareValidationError
, mentre ci siamo sbarazzati della riga"this.name = ..."
nel costruttore.
Wrapping exceptions
Lo scopo della funzionereadUser
nel codice sopra è “leggere i dati dell’utente”. Ci possono verificarsi diversi tipi di errori nel processo., In questo momento abbiamo SyntaxError
eValidationError
, ma in futuroreadUser
la funzione potrebbe crescere e probabilmente generare altri tipi di errori.
Il codice che chiama readUser
dovrebbe gestire questi errori. In questo momento utilizza piùif
nel bloccocatch
, che controllano la classe e gestiscono errori noti e riprendono quelli sconosciuti.
Lo schema è così:
Nel codice sopra possiamo vedere due tipi di errori, ma ce ne possono essere di più.,
Se la funzione readUser
genera diversi tipi di errori, allora dovremmo chiederci: vogliamo davvero controllare tutti i tipi di errore uno per uno ogni volta?
Spesso la risposta è “No”: vorremmo essere”un livello sopra tutto”. Vogliamo solo sapere se c’è stato un “errore di lettura dei dati” – perché esattamente è successo è spesso irrilevante (il messaggio di errore lo descrive). O, ancora meglio, ci piacerebbe avere un modo per ottenere i dettagli dell’errore, ma solo se ne abbiamo bisogno.
La tecnica che descriviamo qui si chiama “wrapping exceptions”.,
- Creeremo una nuova classe
ReadError
per rappresentare un errore generico di “lettura dei dati”. - La funzione
readUser
catturerà gli errori di lettura dei dati che si verificano al suo interno, comeValidationError
eSyntaxError
, e genererà invece unReadError
. - L’oggetto
ReadError
manterrà il riferimento all’errore originale nella sua proprietàcause
.,
Quindi il codice che chiama readUser
dovrà solo controllare ReadError
, non per ogni tipo di errore di lettura dei dati. E se ha bisogno di maggiori dettagli su un errore, può controllare la sua proprietà cause
.,
Ecco il codice che definisce ReadError
e dimostra il suo uso nel readUser
e try..catch
:
Nel codice di cui sopra, readUser
funziona esattamente come descritto catture sintassi e gli errori di convalida e getta ReadError
errori invece (sconosciuto errori sono rigenerata, come al solito).
Quindi il codice esterno controllainstanceof ReadError
e il gioco è fatto. Non c’è bisogno di elencare tutti i possibili tipi di errore.,
L’approccio è chiamato “wrapping exceptions”, perché prendiamo eccezioni di “basso livello” e le “avvolgiamo” inReadError
che è più astratto. È ampiamente usato nella programmazione orientata agli oggetti.
Sommario
- Possiamo ereditare da
Error
e altre classi di errore built-in normalmente. Dobbiamo solo occuparci della proprietàname
e non dimenticare di chiamaresuper
. - Possiamo usare
instanceof
per verificare la presenza di errori particolari. Funziona anche con l’ereditarietà., Ma a volte abbiamo un oggetto error proveniente da una libreria di terze parti e non c’è un modo semplice per ottenere la sua classe. Quindi la proprietàname
può essere utilizzata per tali controlli. - Il wrapping delle eccezioni è una tecnica diffusa: una funzione gestisce eccezioni di basso livello e crea errori di livello superiore invece di vari di basso livello. Le eccezioni di basso livello a volte diventano proprietà di quell’oggetto come
err.cause
negli esempi precedenti, ma non è strettamente richiesto.