攔截器
在Angular
項目中其實有着十分重要的地位,攔截器能夠統一對 HTTP 請求進行攔截處理,咱們能夠在每一個請求體或者響應後對應的流添加一系列動做或者處理數據,再返回給使用者調用。typescript
每一個 API 調用的時候都不可避免的會出現網絡超時的狀況,可是這種狀況是多變的,多是網絡問題,也有多是服務端問題,儘管如此,咱們也只需對網絡超時
這一種狀況來進行處理。後端
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http' import { Injectable } from '@angular/core' import { Observable } from 'rxjs' import { timeout } from 'rxjs/operators' /** 攔截器 - 超時以及重試設置 */ @Injectable() export class TimeoutInterceptor implements HttpInterceptor { constructor() { } intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return next.handle(req) } }
rxjs
確實功能強大,這裏的超時咱們只須要使用timeout
操做符即可以實現。這裏的超時處理邏輯是掛到next.handle()
返回的可觀察對象中。服務器
next 對象表示攔截器鏈表中的下一個攔截器。 這個鏈表中的最後一個 next 對象就是 HttpClient 的後端處理器(backend handler),它會把請求發給服務器,並接收服務器的響應。
大多數的攔截器都會調用 next.handle(),以便這個請求流能走到下一個攔截器,並最終傳給後端處理器。
先在類外部定義一個超時時限網絡
/** 超時時間 */ const DEFAULTTIMEOUT = 8000
在攔截器主函數handle
流中加入操做符antd
return next.handle(req).pipe( timeout(DEFAULTTIMEOUT) )
其實這樣就實現了超時攔截器,當超過設定的時間尚未響應數據的時候,handle流
便會在拋出相應的超時錯誤。ide
在超時錯誤發生後,咱們可能須要第一時間捕獲到以便給用戶一個提示。這裏能夠直接使用catchError
操做符。函數
在攔截器主函數handle流中加入操做符ui
return next.handle(req).pipe( //... 已有的代碼忽略 catchError((err: HttpErrorResponse) => { this.nzNotificationService.error('網絡超時','請重試') return throwError(err) }) )
handle
須要返回一個可觀察對象,因此咱們順便把捕獲的錯誤返回。這樣一來,即可以在捕獲到超時的時候顯示一個簡單的提示。this
通常來講,超時出現的狀況是不肯定的,即便多了提示,有些請求用戶也沒有其餘的動做去重試,只能刷新頁面,那此時從新請求就顯得重要了,咱們能夠在捕獲到超時請求以後對這個請求再進行固定次數的重試,避免某些狀況的超時影響用戶體驗。code
對流進行屢次重試,可使用retryWhen
操做符。
retryWhen
操做符接受一個函數做爲參數,這個函數會接受一個由一組錯誤組成的Observable
,咱們能夠針對這個Observable
作一些節奏控制來促動重試動做,而後在函數中返回這個可觀察對象。
一個簡單的retryWhen
組成:
retryWhen(err$ => { return err$.pipe( //一些節奏控制 ... ) })
如此以來,咱們就能夠直接使用此操做符來實現了。
retryWhen
重試咱們在next.handle
流掛上retryWhen
操做符
return next.handle(req).pipe( //... 已有的代碼忽略 retryWhen(err$ => { return err$ }) )
其實此時就已經實現了重試機制,可是運行結果你會發現,當超時錯誤永遠存在時,重試的次數是無限的,也就是程序會不斷得請求,由於咱們尚未作任何的節奏控制。
那麼,咱們就須要先肯定一下重試的節奏,好比最大的重試次數、每次延遲多久重試、重試上限次數仍是失敗了的處理等等。那就簡單處理提到的這3個狀況吧。
既然retryWhen
中err$
是一個錯誤組成的流,那麼每一次超時重試失敗後,err$
便會推進一次數據,咱們可使用scan
操做符來累計獲取重試失敗的次數,以此來控制重試的最大次數。
scan
操做符接受兩個參數,第一個是累加函數,能夠在函數中獲取上一次scan
的累加值以及所在流的數據,第二個值接受一個scan
的初始累加值,因此能夠很輕鬆地獲取重試錯誤的次數。
在攔截器類外部定義一個最大重試次數:
/** 最大重試次數 */ const MAXRETRYCOUNT = 3
咱們在retryWhen
中掛上scan
操做符
return next.handle(req).pipe( //... 已有的代碼忽略 retryWhen(err$ => { return err$.pipe( scan((errCount, err) => { if (errCount >= MAXRETRYCOUNT) { throw err } return errCount + 1 }, 0) ) }) )
在scan
中,咱們獲取了累加值(errCount
,初始爲0 ),判斷是否大於上限,若是大於便直接拋出超時錯誤(err
),若是小於便返回累加值 +1。至此,攔截器只會再重試到最大次數仍是失敗的狀況下拋出超時錯誤。
重試最好加上延遲,避免某些場景下必定請求錯誤的狀況,好比服務器的某些請求過濾。延遲十分簡單,只須要在err$
掛上delay
操做符,流的推進便會以必定的間隔實行。
return next.handle(req).pipe( //... 已有的代碼忽略 retryWhen(err$ => { return err$.pipe( //... 已有的代碼忽略 delay(1000) ) }) )
可能有的時候網絡太慢,或者重試次數設置得比較大,這樣在請求重試的時候會耗時比較久,而用戶是不知道此時正在重試的,因此加一個友好的提示能夠增長用戶體驗。
而添加提示是屬於比較透明或者說屬於反作用動做,此時咱們能夠直接使用tap
操做符來進行操做。因爲是掛到scan
以後,因此在tap
中獲取到的就是重試的累加值。
return next.handle(req).pipe( //... 已有的代碼忽略 retryWhen(err$ => { return err$.pipe( //... 已有的代碼忽略 tap(errCount => { if(errCount == 1){ //第一次重試時顯示友好信息 this.nzNotificationService.info('網絡超時','正在從新請求中...') } }) ) }) )
這樣當第一次從新請求時,咱們便給出明確的提示。
catchError
)的順序前面咱們在沒有重試功能以前設置了捕獲錯誤,並給出提示。因爲後面加了重試功能,故捕獲錯誤的操做須要掛到重試以後,這樣一來,才能夠在所有重試完成後仍然失敗的狀況下提示用戶,而不是每次重試都給出捕獲到的錯誤提示。
return next.handle(req).pipe( timeout( ... ), retryWhen( ... ), catchError( ... ) )
完成上述步驟,一個簡單的網絡請求超時與重試的攔截器便實現了。完整的代碼以下:
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse } from '@angular/common/http' import { Injectable } from '@angular/core' import { Observable, throwError } from 'rxjs' import { timeout, delay, retryWhen, scan, tap, catchError } from 'rxjs/operators' import { NzNotificationService } from 'ng-zorro-antd' /** 超時時間 */ const DEFAULTTIMEOUT = 8 /** 最大重試次數 */ const MAXRETRYCOUNT = 3 //攔截器 - 超時以及重試設置 @Injectable() export class TimeoutInterceptor implements HttpInterceptor { constructor( private nzNotificationService:NzNotificationService ) { } intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return next.handle(req).pipe( timeout(DEFAULTTIMEOUT), retryWhen(err$ => { //重試 節奏控制器 return err$.pipe( scan((errCount, err) => { if (errCount >= MAXRETRYCOUNT) { throw err } return errCount + 1 }, 0), delay(1000), tap(errCount => { //反作用 if(errCount == 1){ //第一次重試時顯示友好信息 this.nzNotificationService.info('網絡超時','正在從新請求中...') } }) ) }), catchError((err: HttpErrorResponse) => { this.nzNotificationService.error('網絡超時','請重試') return throwError(err) }) ) } }
詳細攔截器說明請前往官網文檔:攔截請求和響應