予想外を期待する – Angularにおけるエラー処理のベストプラクティス

“To expect the unexpected shows a thoroughly modern intellect.” (予想外を期待することは、完全に現代的な知性を示している) – Oscar Wilde

この記事は、Angular でのエラー処理の一元化についてです。

  • client-side errors
  • server-side errors
  • user notification
  • tracking errors

途中でいくつかのコード スニペットを提示し、最後にフル例へのリンクを提供します。

スペイン語版:

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

エラーについて誰のせいにすべきか?このセクションへのリンク

なぜアプリケーションにエラーがあるのでしょうか? なぜ常に動作するコードを仕様から書けないのでしょうか。

結局のところ、人間がソフトウェアを作成するので、間違いを犯しやすいのです。 エラーの背景には、次のような理由が考えられます:

  1. アプリケーションの複雑さ
  2. 関係者間のコミュニケーション
  3. 開発者のミス
  4. 時間の圧力
  5. テストの欠如

このリストは延々と続くことでしょう。 これを念頭に置いて、予期しないことが起こり、エラーがスローされるときがやってきます。

Catch them if you canこのセクションへのリンク

コード内の同期例外をキャッチするには、try/catchブロックを追加します。 もしエラーが try 内で投げられたら、それを catch 処理します。 これを行わないと、スクリプトの実行が停止します。

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

理解できるように、これは非常に速く持続不可能になります。 コード内のあらゆる場所でエラーをキャッチしようとすることはできません。

Catch’em allこのセクションへのリンク

幸いにも、Angular は ErrorHandler で集中的に例外を処理するフックを提供しています。

ErrorHandler:

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

そして、アプリケーションでデフォルトの動作を変更するためにルート モジュールでそれを提供します。

<>Copy
@NgModule({ providers: })

したがって、エラー処理用のコードを変更する場所は 1 つだけとなります。

  1. message – エラーに関する人間が読める説明。
  2. stack – エラーを引き起こした「原因」となったファイルの履歴 (コールスタック) を含む、エラーのスタック トレースです。

一般に、エラー メッセージを記述しない場合、message プロパティはユーザーに表示されるものです。 JavaScript のエラーと同様に、通知に使用できる message プロパティを持ちます。

また、エラーのステータス コードが返されます。 これらはさまざまなタイプがあります。 4 で始まる場合 (4xx) は、クライアントが予期しないことを行ったということです。 たとえば、ステータス 400 (Bad Request) が表示された場合、クライアントが送信したリクエストは、サーバーが期待していたものではありませんでした。

ステータス 5 (5xx) で始まるものは、サーバーのエラーです。 最も典型的なものは 500 Internal Server Error で、サーバー上で何かがうまくいかなかったことを意味する非常に一般的な HTTP ステータス コードですが、サーバーは正確な問題が何であるかについてより具体的に説明することができませんでした。

異なる種類のエラーでは、メッセージとスタック トレースを解析するサービスを使用すると便利です。 この例は非常に単純化されています。 より高度な使用例では、stacktrace.js のようなものを使用できます。

このサービスのロジックは、バックエンドから受け取るエラーの種類に依存します。 また、ユーザーに表示するメッセージの種類にも依存します。

通常、ユーザーにスタック トレースを表示することはありません。 しかし、実稼働環境でない場合、テスト担当者にスタック トレースを表示することがあります。 そのようなシナリオでは、スタック トレースを表示するフラグを設定できます。

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

HttpInterceptor このセクションへのリンク

HttpInterceptor は Angular 4.3.1 で導入されました。 これは、HTTP リクエストとレスポンスをインターセプトして、それらを渡す前に変換または処理する方法を提供します。

インターセプターで実装できる使用例が 2 つあります。

最初に、エラーをスローする前に HTTP 呼び出しを一度または複数回再試行することができます。 たとえば、タイムアウトが発生した場合、例外をスローせずに続行できます。

このために、RxJS の retry 演算子を使用して observable に再サブスクライブするようにします。

この種の動作のより高度な例:

  • Retry an observable sequence on error based on custom criteria
  • Power of RxJS when using exponential backoff

次に、例外の状態をチェックして、それが 401 未承認エラーであるかどうかを確認することができます。 トークン ベースのセキュリティでは、トークンのリフレッシュを試みることができます。 これがうまくいかない場合、ユーザーをログイン ページにリダイレクトできます。

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

ここで、エラー状態をチェックしてエラーを再投入する前に一度再試行しています。 セキュリティ トークンのリフレッシュはこの記事の範囲外です。

作成したインターセプターも提供する必要があります。

<>Copy
providers:

通知このセクションへのリンク

通知には 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: }); }}

これで、エラーが発生したときにユーザーに対して簡単な通知を行います。

サーバー側とクライアント側のエラーは異なる方法で処理することができます。 通知の代わりに、エラー ページを表示できます。

エラー メッセージこのセクションへのリンク

エラー メッセージは重要であり、したがって、ユーザーが先に進むのを助けるために何らかの意味を持つ必要があります。 5346>

それに比べて、代わりに「申し訳ありませんが、お金がありません」のようなものを表示すると、ユーザーはエラーが何であるかを知ることができます。

さらによい解決策は、もっとお金を送金するように指示し、送金ページへのリンクを与えることです。

エラー処理は悪い UX の代用品ではないことを忘れないでください。 ユーザーがエラーをスローする何かを行うことができるなら、それを修正します!

そのために素晴らしいエラー メッセージを作成したからといって、エラーをスルーしてはいけません。

ログ記録 このセクションへのリンク

エラーを記録しない場合、エラーにぶつかったユーザーだけがそのことを知ることになります。 情報を保存することは、後で問題のトラブルシューティングを行うために必要です。

データを保存することを決定したら、それを保存する方法も選択する必要があります。 詳細は後述します。

データはどこに保存すべきでしょうか。

集中型のエラー処理では、決定を後回しにしてもあまり後悔する必要はありません。 今、私たちはコードを変更する場所を 1 つだけ持っています。 とりあえず、コンソールにメッセージを記録してみましょう。

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

Error TrackingLink to this section

理想的には、ユーザーがバグを見つける前に Web アプリケーションでバグを識別したいものです。 エラー追跡は、積極的に問題を特定し、できるだけ早く修正するプロセスです。

ですから、ただ座っていて、ユーザーがエラーを報告してくれることを期待するわけにはいきません。 その代わり、エラーをログに記録して監視することにより、積極的になるべきです。

エラーが発生したら、それを知るべきです。 しかし、この分野を専門とする Bugsnag、Sentry、TrackJs、および Rollbar のような優れたサービスが多数ある中で、車輪を再発明する必要はありません。

これらのフロントエンド エラー追跡ソリューションのいずれかを使用すると、ユーザー セッションを記録および再生できるので、ユーザーが経験したことを正確に確認することができます。

バグを再現できなければ、それを修正することはできません。

言い換えると、適切なエラー追跡ソリューションでは、エラーが発生すると警告し、問題を再現/解決する方法についての洞察を提供できます。 例として、ここでそれを使用できます。

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

より堅牢なソリューションを実装することは、この記事の範囲外です。 このため、サービスのコンストラクターで依存性注入を使用することはできません。 代わりに、Injector を使用して手動で注入する必要があります。

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

まとめこのセクションへのリンク

エラー処理はエンタープライズ アプリケーションの基礎となるものです。 この記事では、ErrorHandler のデフォルトの動作をオーバーライドすることで、それを一元化しました。

  1. Error サービスはメッセージとスタック トレースを解析します。
  2. Notification サービスはエラーについてユーザーに通知します。
  3. Logging サービスはエラーを記録します。

このソリューションを使用すると、エラーの追跡を開始でき、できればユーザーに良いエクスペリエンスを提供できます。

サンプル コードは GitHub にあります。 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
  • Discuss with community

    Leave a Reply