Odottamattomuuden odottaminen – Parhaat käytännöt virheiden käsittelyyn Angularissa
”Odottamattomuuden odottaminen on osoitus modernista älystä.” – Oscar Wilde
Tämä artikkeli käsittelee virheenkäsittelyn keskittämistä Angularissa. Käsittelen joitakin yleisempiä aiheita, kuten:
- asiakaspuolen virheet
- palvelinpuolen virheet
- käyttäjäilmoitukset
- seurantavirheet
esittelen joitain koodinpätkiä matkan varrella ja lopuksi annan linkin koko esimerkkiin.
Espanjankielinen versio:
- Esperando lo Inesperado – Buenas prácticas para el manejo de errores en Angular
Ketä meidän pitäisi syyttää virheistä?Linkki tähän osioon
Miksi sovelluksissamme on virheitä? Miksi emme voi kirjoittaa spesifikaatioista koodia, joka toimii aina?
Loppujen lopuksi ihmiset luovat ohjelmistoja, ja olemme alttiita tekemään virheitä. Joitakin syitä virheiden taustalla voi olla:
- Sovelluksen monimutkaisuus
- Sidosryhmien välinen kommunikaatio
- Kehittäjän virheet
- Aikapaine
- Testauksen puute
Luettelo voisi jatkua loputtomiin. Tätä silmällä pitäen tulee aika, jolloin tapahtuu odottamatonta ja heitetään virhe.
Sieppaa ne, jos pystytLinkki tähän jaksoon
Koodissa olevien synkronisten poikkeusten sieppaamiseksi voimme lisätä try/catch
-lohkon. Jos try
sisällä catch
heitetään virhe, niin catch
käsittelemme sen. Jos emme tee tätä, skriptin suoritus pysähtyy.
<>Kopioitry { throw new Error('En error happened');}catch (error) { console.error('Log error', error);}console.log('Script execution continues');
Ymmärrettävästi tästä tulee kestämätöntä hyvin nopeasti. Emme voi yrittää saada virheitä kiinni kaikkialla koodissa. Tarvitsemme globaalia virheenkäsittelyä.
Catch’em allLinkki tähän osioon
Onneksi Angular tarjoaa koukun keskitettyyn poikkeuskäsittelyyn ErrorHandlerin avulla.
Default-toteutus
ErrorHandler
tulostaa virheilmoituksetconsole
.
Voidaan muuttaa tätä käyttäytymistä luomalla luokka, joka toteuttaa ErrorHandlerin:
import { ErrorHandler } from '@angular/core';@Injectable()export class GlobalErrorHandler implements ErrorHandler { handleError(error) { // your custom error handling logic }}
Sitten tarjoamme sen juurimoduulissamme muuttaaksemme sovelluksemme oletuskäyttäytymistä. Oletusarvoisen ErrorHandler-luokan sijaan käytämme meidän luokkaamme.
<>Copy@NgModule({ providers: })
Siten meillä on nyt vain yksi paikka, jossa virheenkäsittelyn koodia on muutettava.
Client-puolen virheetLinkki tähän osioon
Client-puolella, kun jotain odottamatonta sattuu, JavaScript-virhehehe heitetään. Sillä on kaksi tärkeää ominaisuutta, joita voimme käyttää.
- message – Ihmisen luettavissa oleva kuvaus virheestä.
- stack – Virheen pinojälki, jossa on historiatieto (kutsupino) siitä, mitkä tiedostot ’vastasivat’ kyseisen Virheen aiheuttamisesta.
Tyypillisesti message-ominaisuus on se, mitä näytämme käyttäjälle, jos emme kirjoita virheilmoituksia.
Palvelinpuolen virheetLinkki tähän osioon
Palvelinpuolella, kun jokin menee pieleen, palautetaan HttpErrorResponse. Kuten JavaScript-virheellä, sillä on message-ominaisuus, jota voimme käyttää ilmoituksiin.
Se palauttaa myös virheen tilakoodin. Nämä voivat olla erityyppisiä. Jos se alkaa nelosella (4xx), asiakas teki jotain odottamatonta. Jos saamme esimerkiksi statuksen 400 (Bad Request), asiakkaan lähettämä pyyntö ei ollut sellainen kuin palvelin odotti.
Viidellä (5xx) alkavat statukset ovat palvelimen virheitä. Tyypillisin on 500 Internal Server Error, hyvin yleinen HTTP-tilakoodi, joka tarkoittaa, että palvelimella on mennyt jotain pieleen, mutta palvelin ei osannut tarkemmin sanoa, mikä ongelma tarkalleen ottaen on.
Erilaisia virheitä varten on hyödyllistä käyttää palvelua, joka jäsentää viestejä ja pinojälkiä niistä.
Virheiden käsittelypalveluLinkki tähän osioon
Tälle palvelulle lisäämme logiikan palvelimelta ja asiakkaalta tulevien virheviestien ja pinojälkien jäsentämiseen. Tämä esimerkki on hyvin pelkistetty. Edistyneempiin käyttötapauksiin voisimme käyttää esimerkiksi stacktrace.js:ää.
Tämän palvelun logiikka riippuu siitä, millaisia virheitä saamme backendistä. Se riippuu myös siitä, minkälaisen viestin haluamme näyttää käyttäjillemme.
Tavallisesti emme näytä pinojälkeä käyttäjillemme. Jos emme kuitenkaan ole tuotantoympäristössä, saatamme haluta näyttää pinojäljen testaajille. Siinä skenaariossa voimme asettaa lipun, joka näyttää pinojäljen.
<>Kopioiimport { Injectable } from '@angular/core';import { HttpErrorResponse } from '@angular/common/http';@Injectable({ providedIn: 'root'})export class ErrorService { getClientMessage(error: Error): string { if (!navigator.onLine) { return 'No Internet Connection'; } return error.message ? error.message : error.toString(); } getClientStack(error: Error): string { return error.stack; } getServerMessage(error: HttpErrorResponse): string { return error.message; } getServerStack(error: HttpErrorResponse): string { // handle stack trace return 'stack'; }}
HttpInterceptorLinkki tähän osioon
HttpInterceptor otettiin käyttöön Angular 4.3.1:n myötä. Se tarjoaa tavan siepata HTTP-pyynnöt ja -vastaukset muuntamaan tai käsittelemään niitä ennen niiden välittämistä eteenpäin.
Se on kaksi käyttötapausta, jotka voimme toteuttaa sieppaajalla.
Ensiksi voimme yrittää HTTP-kutsua uudelleen kerran tai useita kertoja ennen kuin heitämme virheen. Joissain tapauksissa, esimerkiksi jos saamme aikakatkaisun, voimme jatkaa ilman poikkeuksen heittämistä.
Tässä käytämme RxJS:n retry-operaattoria havaintokohteen uudelleenkirjoittamiseen.
Edistyneempiä esimerkkejä tämänkaltaisesta käyttäytymisestä:
- Havainnoitavan sekvenssin uudelleenkäsittely virheen sattuessa mukautettujen kriteerien perusteella
- RxJS:n teho käytettäessä eksponentiaalista takaisinkytkentäoperaattoria
Voitamme tämän jälkeen tarkastaa poikkeuksen tilan ja nähdä, onko kyseessä 401-virhe, joka ei ole sallittu. Token-pohjaisella suojauksella voimme yrittää päivittää tokenin. Jos tämä ei onnistu, voimme ohjata käyttäjän uudelleen kirjautumissivulle.
<>Kopioiimport { Injectable } from '@angular/core';import { HttpEvent, HttpRequest, HttpHandler, HttpInterceptor, HttpErrorResponse } from '@angular/common/http';import { Observable, throwError } from 'rxjs';import { retry, catchError } from 'rxjs/operators';@Injectable()export class ServerErrorInterceptor implements HttpInterceptor { intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return next.handle(request).pipe( retry(1), catchError((error: HttpErrorResponse) => { if (error.status === 401) { // refresh token } else { return throwError(error); } }) ); }}
Tässä yritämme kerran uudelleen, ennen kuin tarkistamme virhetilan ja heitämme virheen uudelleen. Tietoturvatunnisteiden päivittäminen ei kuulu tämän artikkelin piiriin.
Meidän on myös annettava luomaamme sieppaajaa.
<>Copyproviders:
IlmoituksetLinkki tähän osioon
Ilmoituksiin käytän Angularin materiaalin Snackbaria.
<>Copyimport { Injectable} from '@angular/core';import { MatSnackBar } from '@angular/material/snack-bar';@Injectable({ providedIn: 'root'})export class NotificationService { constructor(public snackBar: MatSnackBar) { } showSuccess(message: string): void { this.snackBar.open(message); } showError(message: string): void { // The second parameter is the text in the button. // In the third, we send in the css class for the snack bar. this.snackBar.open(message, 'X', {panelClass: }); }}
Tämän avulla meillä on yksinkertaiset ilmoitukset käyttäjälle virheiden ilmetessä.
Voidaan käsitellä palvelin- ja asiakaspuolen virheitä eri tavalla. Ilmoitusten sijaan voimme näyttää virhesivun.
VirheilmoitusLinkki tähän osioon
Virheilmoituksilla on merkitystä, ja siksi niillä pitäisi olla jokin merkitys, joka auttaa käyttäjää eteenpäin. Näyttämällä ”Tapahtui virhe” emme kerro käyttäjälle, mikä ongelma on tai miten se ratkaistaan.
Vertauksena, jos sen sijaan näytämme jotain sellaista kuin ”Anteeksi, rahat ovat loppu.”, käyttäjä tietää, mistä virheestä on kyse. Hieman parempi, mutta se ei auta häntä ratkaisemaan virhettä.
Jopa parempi ratkaisu olisi kertoa käyttäjälle, että hänen on siirrettävä lisää rahaa, ja antaa linkki rahansiirtosivulle.
Muista, että virheiden käsittely ei korvaa huonoa UX:ää.
Mitä tarkoitan tällä, on se, että odotettuja virheitä ei pitäisi olla. Jos käyttäjä voi tehdä jotain, joka aiheuttaa virheen, korjaa se!
Älä päästä virhettä läpi vain siksi, että olet luonut sille kivan virheilmoituksen.
LoggaaminenLinkki tähän osioon
Jos emme kirjaa virheitä lokiin, vain niihin törmäävä käyttäjä tietää niistä. Tietojen tallentaminen on välttämätöntä, jotta voimme myöhemmin korjata vian.
Kun olemme päättäneet tallentaa tiedot, meidän on myös valittava, miten tallennamme ne. Siitä lisää myöhemmin.
Mihin tallennamme tiedot?
Keskitetyn virheenkäsittelyn ansiosta meidän ei tarvitse olla kovin pahoillamme siitä, että jätämme päätöksen myöhemmäksi. Meillä on nyt vain yksi paikka, jossa voimme muuttaa koodiamme. Toistaiseksi kirjaamme viestin konsoliin.
<>Kopioiimport { Injectable } from '@angular/core';@Injectable({ providedIn: 'root'})export class LoggingService { logError(message: string, stack: string) { // Send errors to be saved here // The console.log is only for testing this example. console.log('LoggingService: ' + message); }}
Virheiden seurantaLinkki tähän osioon
Haluat tunnistaa web-sovelluksessasi esiintyvät virheet ennen kuin käyttäjät kohtaavat ne. Virheiden seuranta on prosessi, jossa ongelmat tunnistetaan ennakoivasti ja korjataan mahdollisimman nopeasti.
Emmekä siis voi vain istua ja odottaa, että käyttäjät ilmoittavat meille virheistä. Sen sijaan meidän pitäisi toimia ennakoivasti kirjaamalla ja seuraamalla virheitä.
Meidän pitäisi tietää virheistä, kun niitä tapahtuu.
Voisimme luoda ratkaisumme tätä tarkoitusta varten. Miksi kuitenkin keksiä pyörä uudelleen, kun on niin monia erinomaisia palveluita, kuten Bugsnag, Sentry, TrackJs ja Rollbar, jotka ovat erikoistuneet tälle alalle.
Käyttämällä jotakin näistä front-end-virheiden seurantaratkaisuista voit tallentaa ja toistaa käyttäjäistuntoja, jotta voit itse nähdä tarkalleen, mitä käyttäjä koki.
Jos et voi toistaa virhettä, et voi korjata sitä.
Muilla sanoilla kunnollinen virheiden seurantaratkaisu voi hälyttää sinua, kun virhe tapahtuu, ja antaa sinulle tietoa siitä, miten voit toistaa/ratkaista ongelman.
Ammassa aiemmassa artikkelissa Kuinka lähettää virheet Slackiin Angularissa puhuin Slackin käyttämisestä virheiden seuraamiseen. Esimerkkinä voisimme käyttää sitä tässä:
<>Kopioiimport { Injectable } from '@angular/core';import { SlackService } from './slack.service';@Injectable({ providedIn: 'root'})export class LoggingService { constructor(private slackService: SlackService) { } logError(message: string, stack: string) { this.slackService.postErrorOnSlack(message, stack); }}
Kestävämmän ratkaisun toteuttaminen ei kuulu tämän artikkelin piiriin.
Kaikki yhdessä nytLinkki tähän osioon
Koska virheenkäsittely on olennaista, se ladataan ensin. Tämän takia emme voi käyttää riippuvuusinjektiota palveluiden konstruktorissa. Sen sijaan meidän on injektoitava ne manuaalisesti Injectorilla.
<>Kopioiimport { ErrorHandler, Injectable, Injector } from '@angular/core';import { HttpErrorResponse } from '@angular/common/http';import { LoggingService } from './services/logging.service';import { ErrorService } from './services/error.service';import { NotificationService } from './services/notification.service';@Injectable()export class GlobalErrorHandler implements ErrorHandler { // Error handling is important and needs to be loaded first. // Because of this we should manually inject the services with Injector. constructor(private injector: Injector) { } handleError(error: Error | HttpErrorResponse) { const errorService = this.injector.get(ErrorService); const logger = this.injector.get(LoggingService); const notifier = this.injector.get(NotificationService); let message; let stackTrace; if (error instanceof HttpErrorResponse) { // Server Error message = errorService.getServerMessage(error); stackTrace = errorService.getServerStack(error); notifier.showError(message); } else { // Client Error message = errorService.getClientMessage(error); stackTrace = errorService.getClientStack(error); notifier.showError(message); } // Always log errors logger.logError(message, stackTrace); console.error(error); }}
JohtopäätösLinkki tähän jaksoon
Virheenkäsittely on yrityssovelluksen kulmakivi. Tässä artikkelissa keskitimme sen ohittamalla ErrorHandlerin oletuskäyttäytymisen. Sen jälkeen lisäsimme useita palveluita:
- Virhepalvelu, joka jäsentää viestit ja pinojäljet.
- Ilmoituspalvelu, joka ilmoittaa käyttäjille virheistä.
- Loggauspalvelu, joka kirjaa virheet lokiin.
Toteutimme myös sieppaaja-luokan, jonka avulla voitiin:
- Kysellä, ennen kuin heität virheen.
- Tarkistaa tietyt virhetapaukset ja reagoida niihin sen mukaisesti.
Tämän ratkaisun avulla voit alkaa seurata virheitä ja toivottavasti antaa käyttäjille paremman käyttökokemuksen.
Esimerkkikoodi GitHubissa.
Koodin ajaminen StackBlitzissä.
LähteetLinkki tähän osioon
- Virheidenkäsittely & Angular by Aleix Suau
- Virheidenkäsittely Javascriptissä: The Definitive Guide by Lukas Gisder-Dubé
- Global Error Handling with Angular 2+ by Austin
- Angular applications – error handling and elegant recovery by Brachi Packter
- The Art of the Error Message by Marina Posniak
- JavaScript Weekly: Graceful Error Handling by Severin Perez
Keskustelua yhteisön kanssa
Leave a Reply