Očekávání neočekávaného – osvědčené postupy pro zpracování chyb v Angularu

„Očekávání neočekávaného svědčí o naprosto moderním intelektu.“ – Oscar Wilde

Tento článek se zabývá centralizací zpracování chyb v systému Angular. Probírám některá častější témata, jako jsou:

  • chyby na straně klienta
  • chyby na straně serveru
  • oznámení uživateli
  • sledování chyb

Během cesty uvádím několik úryvků kódu a nakonec uvádím odkaz na celý příklad.

Španělská verze:

  • Esperando lo Inesperado – Buenas prácticas para el manejo de errores en Angular

Koho máme vinit z chyb?“ Odkaz na tuto část

Proč máme v aplikacích chyby? Proč nemůžeme na základě specifikací napsat kód, který by vždy fungoval?“

Software nakonec vytvářejí lidé a my jsme náchylní k chybám. Některé důvody vzniku chyb mohou být:

  1. Složitost aplikace
  2. Komunikace mezi zúčastněnými stranami
  3. Chyby vývojáře
  4. Časový tlak
  5. Nedostatek testování

Tento seznam by mohl pokračovat dál a dál. S tímto vědomím přichází čas, kdy se stane něco neočekávaného a dojde k vyhození chyby.

Chytejte je, pokud můžeteOdkaz na tuto sekci

Pro zachycení synchronních výjimek v kódu můžeme přidat blok try/catch. Pokud je uvnitř tryvyhozena chyba, pak ji catchzpracujeme a ošetříme. Pokud to neuděláme, provádění skriptu se zastaví.

<>Kopírování
try { throw new Error('En error happened');}catch (error) { console.error('Log error', error);}console.log('Script execution continues');

To se pochopitelně velmi rychle stane neudržitelným. Nemůžeme se snažit zachytit chyby všude v kódu. Potřebujeme globální ošetření chyb.

Catch’em allOdkaz na tuto část

Naštěstí Angular poskytuje háček pro centralizované ošetření výjimek pomocí ErrorHandler.

Výchozí implementace ErrorHandler vypisuje chybové zprávy do console.

Toto chování můžeme změnit vytvořením třídy, která implementuje ErrorHandler:

<>Kopie
import { ErrorHandler } from '@angular/core';@Injectable()export class GlobalErrorHandler implements ErrorHandler { handleError(error) { // your custom error handling logic }}

Poté ji poskytneme v našem kořenovém modulu, abychom změnili výchozí chování v naší aplikaci. Místo výchozí třídy ErrorHandler použijeme naši třídu.

<>Copy
@NgModule({ providers: })

Máme tedy nyní pouze jedno místo, kde změnit kód pro zpracování chyb.

Chyby na straně klientaOdkaz na tuto část

Na straně klienta, když se stane něco neočekávaného, je vyhozena chyba JavaScriptu. Má dvě důležité vlastnosti, které můžeme použít.

  1. message – Lidsky čitelný popis chyby.
  2. stack – Error stack trace s historií (call stack) toho, jaké soubory byly „zodpovědné“ za způsobení této chyby.

Typicky se vlastnost message zobrazuje uživateli, pokud nepíšeme naše chybové hlášení.

Chyby na straně serveruOdkaz na tuto sekci

Na straně serveru, když se něco pokazí, je vrácena HttpErrorResponse. Stejně jako u chyby JavaScriptu má vlastnost message, kterou můžeme použít pro oznámení.

Vrací také stavový kód chyby. Ty mohou být různých typů. Pokud začíná čtyřkou (4xx), pak klient provedl něco neočekávaného. Pokud například dostaneme stav 400 (Bad Request), pak požadavek, který klient odeslal, nebyl takový, jaký server očekával.

Stavy začínající pětkou (5xx) jsou chyby serveru. Nejtypičtější je 500 Internal Server Error, což je velmi obecný stavový kód HTTP, který znamená, že se na serveru něco pokazilo, ale server nedokázal blíže specifikovat, v čem přesně problém spočívá.

Při různých typech chyb se hodí služba, která z nich analyzuje zprávy a stack traces.

Služba ErrorOdkaz na tuto část

V této službě přidáme logiku pro analýzu chybových zpráv a stack traces ze serveru a klienta. Tento příklad je velmi zjednodušený. Pro pokročilejší případy použití bychom mohli použít něco jako stacktrace.js.

Logika v této službě závisí na tom, jaký druh chyb obdržíme z našeho backendu. Záleží také na tom, jaký druh zprávy chceme zobrazit našim uživatelům.

Obvykle stack trace našim uživatelům nezobrazujeme. Pokud však nejsme v produkčním prostředí, můžeme chtít zobrazit stopu zásobníku testerům. V takovém případě můžeme nastavit příznak, který stopu zásobníku zobrazí.

<>Kopírovat
import { 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'; }}

HttpInterceptorOdkaz na tuto sekci

HttpInterceptor byl zaveden s verzí 4.3.1 modulu Angular. Poskytuje způsob, jak zachytit požadavky a odpovědi HTTP a transformovat je nebo zpracovat předtím, než je předáme dál.

Existují dva případy použití, které můžeme v interceptoru implementovat.

Poprvé můžeme volání HTTP zopakovat jednou nebo vícekrát, než vyhodíme chybu. V některých případech, například pokud dostaneme timeout, můžeme pokračovat bez vyhození výjimky.

Pro tento účel použijeme operátor retry z RxJS pro opětovné přihlášení k pozorovatelnému.

Pokročilejší příklady takového chování:

  • Retryování sekvence observable při chybě na základě vlastních kritérií
  • Moc RxJS při použití exponenciálního backoffu

Můžeme pak zkontrolovat stav výjimky a zjistit, zda se jedná o chybu 401 unauthorized. Při zabezpečení pomocí tokenu se můžeme pokusit o obnovení tokenu. Pokud to nefunguje, můžeme uživatele přesměrovat na přihlašovací stránku.

<>Kopírování
import { 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); } }) ); }}

Tady to zkusíme ještě jednou, než zkontrolujeme stav chyby a znovu ji vyhodíme. Obnovení bezpečnostních tokenů je mimo rozsah tohoto článku.

Také musíme poskytnout námi vytvořený interceptor.

<>Copy
providers:

NotifikaceOdkaz na tuto část

Pro notifikace používám Angular Material Snackbar.

<>Copy
import { 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: }); }}

Díky tomu máme jednoduchá oznámení pro uživatele při výskytu chyb.

Chyby na straně serveru a na straně klienta můžeme zpracovávat různě. Místo oznámení můžeme zobrazit chybovou stránku.

Zpráva o chyběOdkaz na tuto sekci

Zprávy o chybách mají význam, a proto by měly mít nějaký smysl, aby uživateli pomohly v dalším postupu. Tím, že zobrazíme „Došlo k chybě“, uživateli neřekneme, v čem je problém nebo jak ho vyřešit.

Pro srovnání, pokud místo toho zobrazíme něco jako „Omlouváme se, došly vám peníze.“, pak uživatel ví, v čem je chyba. Je to o něco lepší, ale nepomůže mu to chybu vyřešit.

Ještě lepším řešením by bylo říct mu, aby převedl další peníze, a dát odkaz na stránku s převodem peněz.

Pamatujte, že zpracování chyb není náhradou za špatné UX.

Tím chci říct, že byste neměli mít žádné očekávané chyby. Pokud uživatel může udělat něco, co vyhodí chybu, tak to opravte!

Nepropouštějte chybu jen proto, že jste pro ni vytvořili pěknou chybovou hlášku.

ZaznamenáváníOdkaz na tuto sekci

Pokud chyby nezaznamenáváme, pak o nich ví jen uživatel, který na ně narazí. Ukládání informací je nezbytné, abychom mohli později problém vyřešit.

Když jsme se rozhodli pro ukládání dat, musíme také zvolit způsob jejich ukládání. O tom později.

Kam máme data uložit?

S centralizovaným zpracováním chyb si nemusíme příliš vyčítat, že jsme rozhodnutí nechali na později. Nyní máme pouze jedno místo, kde můžeme náš kód změnit. Prozatím zaznamenejme zprávu do konzoly.

<>Zkopírujte
import { 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); }}

Sledování chybOdkaz do této sekce

Ve webové aplikaci chcete identifikovat chyby dříve, než se s nimi uživatelé setkají. Sledování chyb je proces proaktivní identifikace problémů a jejich co nejrychlejší opravy.

Nemůžeme tedy jen sedět a očekávat, že nám uživatelé chyby nahlásí. Místo toho bychom měli být proaktivní a chyby zaznamenávat a sledovat.

Měli bychom o chybách vědět, když se vyskytnou.

Pro tento účel bychom mohli vytvořit naše řešení. Proč však znovu vynalézat kolo, když existuje tolik vynikajících služeb, jako jsou Bugsnag, Sentry, TrackJs a Rollbar, které se na tuto oblast specializují.

Použití některého z těchto front-end řešení pro sledování chyb vám může umožnit nahrávat a přehrávat uživatelské relace, takže se můžete sami přesvědčit, co přesně uživatel zažil.

Pokud nemůžete chybu reprodukovat, nemůžete ji ani opravit.

Jinými slovy, správné řešení pro sledování chyb by vás mohlo upozornit na výskyt chyby a poskytnout náhled na to, jak problém replikovat/vyřešit.

V dřívějším článku Jak posílat chyby do Slacku v aplikaci Angular jsem hovořil o použití Slacku ke sledování chyb. Jako příklad bychom jej mohli použít zde:

<>Kopírovat
import { 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); }}

Implementace robustnějšího řešení je mimo rozsah tohoto článku.

Nyní vše pohromaděOdkaz na tuto část

Protože je zpracování chyb zásadní, načte se jako první. Z tohoto důvodu nemůžeme v konstruktoru služeb použít funkci dependency injection. Místo toho je musíme injektovat ručně pomocí injektoru.

<>Kopie
import { 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); }}

ZávěrOdkaz na tuto sekci

Ošetřování chyb je základním kamenem podnikové aplikace. V tomto článku jsme jej centralizovali přepsáním výchozího chování obsluhy chyb ErrorHandler. Poté jsme přidali několik služeb:

  1. Služba Error pro analýzu zpráv a stop zásobníku.
  2. Služba Notification pro upozornění uživatelů na chyby.
  3. Služba Logging pro protokolování chyb.

Také jsme implementovali třídu Interceptor, která:

  1. Retronizuje před vyhozením chyby.
  2. Kontroluje konkrétní chyby a podle toho reaguje.

S tímto řešením můžete začít sledovat chyby a doufejme, že uživatelům poskytnete lepší zážitek.

Ukázkový kód na GitHubu.
Spustit kód na StackBlitz.

ZdrojeOdkaz na tuto část

  • Obsluha chyb & Angular by Aleix Suau
  • Obsluha chyb v Javascriptu:
  • 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

Diskutujte s komunitou

Leave a Reply