资讯专栏INFORMATION COLUMN

HttpInterceptor 拦截器 - 网络请求超时与重试的简单实现

stonezhu / 3150人阅读

摘要:对象表示拦截器链表中的下一个拦截器。至此,拦截器只会再重试到最大次数还是失败的情况下抛出超时错误。完成上述步骤,一个简单的网络请求超时与重试的拦截器便实现了。

...

拦截器Angular项目中其实有着十分重要的地位,拦截器可以统一对 HTTP 请求进行拦截处理,我们可以在每个请求体或者响应后对应的流添加一系列动作或者处理数据,再返回给使用者调用。

每个 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, next: HttpHandler): Observable> {
        return next.handle(req)
    }
}
加入超时处理 超时

rxjs确实功能强大,这里的超时我们只需要使用timeout操作符便可以实现。这里的超时处理逻辑是挂到next.handle()返回的可观察对象中。

next 对象表示拦截器链表中的下一个拦截器。 这个链表中的最后一个 next 对象就是 HttpClient 的后端处理器(backend handler),它会把请求发给服务器,并接收服务器的响应。
大多数的拦截器都会调用 next.handle(),以便这个请求流能走到下一个拦截器,并最终传给后端处理器。

先在类外部定义一个超时时限

/** 超时时间 */
const DEFAULTTIMEOUT = 8000

在拦截器主函数handle流中加入操作符

return next.handle(req).pipe(
    timeout(DEFAULTTIMEOUT)
)

其实这样就实现了超时拦截器,当超过设定的时间还没有响应数据的时候,handle流便会在抛出相应的超时错误。

捕获超时

在超时错误发生后,我们可能需要第一时间捕获到以便给用户一个提示。这里可以直接使用catchError操作符。

在拦截器主函数handle流中加入操作符

return next.handle(req).pipe(
    //... 已有的代码忽略
    catchError((err: HttpErrorResponse) => {
        this.nzNotificationService.error("网络超时","请重试")
        return throwError(err)
    })
)

handle需要返回一个可观察对象,所以我们顺便把捕获的错误返回。这样一来,便可以在捕获到超时的时候显示一个简单的提示。

超时重试

一般来说,超时出现的情况是不确定的,即使多了提示,有些请求用户也没有其他的动作去重试,只能刷新页面,那此时重新请求就显得重要了,我们可以在捕获到超时请求之后对这个请求再进行固定次数的重试,避免某些情况的超时影响用户体验。

对流进行多次重试,可以使用retryWhen操作符。

retryWhen操作符接受一个函数作为参数,这个函数会接受一个由一组错误组成的Observable,我们可以针对这个Observable做一些节奏控制来促动重试动作,然后在函数中返回这个可观察对象。

一个简单的retryWhen组成:

retryWhen(err$ => {
    return err$.pipe(
        //一些节奏控制
        ...
    )
})

如此以来,我们就可以直接使用此操作符来实现了。

添加retryWhen重试

我们在next.handle流挂上retryWhen操作符

return next.handle(req).pipe(
    //... 已有的代码忽略
    retryWhen(err$ => {
        return err$
    })
)

其实此时就已经实现了重试机制,但是运行结果你会发现,当超时错误永远存在时,重试的次数是无限的,也就是程序会不断得请求,因为我们还没有做任何的节奏控制。

那么,我们就需要先确定一下重试的节奏,比如最大的重试次数、每次延迟多久重试、重试上限次数还是失败了的处理等等。那就简单处理提到的这3个情况吧。

重试最大次数

既然retryWhenerr$是一个错误组成的流,那么每一次超时重试失败后,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, next: HttpHandler): Observable> {
        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)
            })
        )
    }   
}

详细拦截器说明请前往官网文档:拦截请求和响应

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/98649.html

相关文章

  • Spring 指南(spring-retry)

    摘要:包含一些状态来决定是重试还是中止,但是这个状态位于堆栈上,不需要将它存储在全局的任何位置,因此我们将此称为无状态重试。将抛出原始异常,除非在有状态的情况下,当没有可用的恢复,在这种情况下,它将抛出。 spring-retry 该项目为Spring应用程序提供声明式重试支持,它用于Spring Batch、Spring Integration、Apache Hadoop的Spring(以...

    xiaotianyi 评论0 收藏0
  • 我们如何在Linkerd 2.2里设计重试

    摘要:在这篇文章中,我们描述了我们如何在里设计重试,使能够在最小化风险的同时,自动提高系统可靠性。配置重试的最常用方法,是指定在放弃之前执行的最大重试次数。超时时,将取消请求并返回响应。但是在上面的服务配置文件中,我们将在服务器端指定重试政策。 showImg(https://segmentfault.com/img/bVbo113?w=4400&h=1007);作者:Alex Leong ...

    Mike617 评论0 收藏0
  • RestTemplate集成Ribbbon

    摘要:的类图如下主要根据创建扩展了,创建拦截的,这里会设置拦截器,这是集成的核心,当发起请求调用的时候,会先经过拦截器,然后才真正发起请求。和是配合使用的,最大重试次数是针对每一个的,如果设置,这样触发最大重试次数就是次。 上一篇文章我们分析了ribbon的核心原理,接下来我们来看看springcloud是如何集成ribbon的,不同的springcloud的组件(feign,zuul,Re...

    wall2flower 评论0 收藏0
  • SpringCloud升级之路2020.0.x版-37. 实现异步的客户端封装配置管理的意义与设计

    摘要:对于异步的请求,使用的是异步客户端即。要实现的配置设计以及使用举例要实现的配置设计以及使用举例首先,我们要实现的,其包含三个重试重试的要在负载均衡之前,因为重试的时候,我们会从负载均衡器获取另一个实例进行重试,而不是在同一个实例上重试多次。 本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 为何需要封装异步 HT...

    fxp 评论0 收藏0
  • 签发的用户认证token超时刷新策略

    摘要:签发的用户认证超时刷新策略这个模块分离至项目权限管理系统与前后端分离实践,感觉那样太长了找不到重点,分离出来要好点。这样在有效期过后的时间段内可以申请刷新。 签发的用户认证token超时刷新策略 这个模块分离至项目api权限管理系统与前后端分离实践,感觉那样太长了找不到重点,分离出来要好点。 对于登录的用户签发其对应的jwt,我们在jwt设置他的固定有效期时间,在有效期内用户携带jw...

    e10101 评论0 收藏0

发表评论

0条评论

stonezhu

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<