基于axios集成前端请求重试功能

请求重试的意义

我们在访问网站的时候,在发起http请求或者访问远程数据库,可能会发生一些异常,有些异常或者错误可能是短时间的网络环境不佳造成,若我们重新发请求也可能会成功获取到数据。

实际运用中,前端会使用一些第三方的库来处理请求,本文就是基于目前流行的axios来集成请求重试。

axios以及拦截器介绍

axios(官网:http://www.axios-js.com/)是一款开源的、设计相对友好的http库,github Star数达到6w多,由于本文重点时拓展重试功能,具体如何使用这里就不赘述了。

axios的设计者提供了请求拦截器(interceptors),可以在在请求或响应被 then 或 catch 处理前拦截它们。有了拦截器,重试功能就非常容易实现了。

axios提供了请求拦截器和响应拦截器,我们先了解下axios拦截器如何工作的(以下代码来自于aixos官网实例):

// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    return response;
  }, function (error) {
    // 对响应错误做点什么
    return Promise.reject(error);
  });

我们要实现HTTP请求重试,在这里要用到响应拦截器,即服务器响应异常的时候去重新发起http请求。捕获到请求异常后,通过error.config能获取到此次请求的配置,然后我们继续用这份配置发起请求 axios(config),就能实现重试了。实际开发中,我发现在某些情况下error.config会为undefined,这样没有办法进行重试了,这点后面会详细分析原因。

重试机制设计

重试机制包括什么情况下进行重试,重试次数,重试延迟时间等。实际发起请求会有很多情况,如何设计重试机制这点也比较重要。但发生请求异常时,我们功过catch到的error对象一般能获取到请求失败时返回的状态码statusCode(正常情况下时200或者304),一般我们比较场景的statusCode有403,404,500,503,504等等。当然还有一些情况下没有statusCode,比如网络异常的情况下会返回 network error。

我们目前设计的重试机制就是:

  • 响应成功,不重试;
  • 响应超时(设计超时时间8000),不重试
  • 响应statusCode 非200或者非304 重试1次

下图是我在实际开发中使在调试过程中使用了Whistle进行接口异常mock以及对应的重试规则。

比如使用statusCode 来mock状态码,实际是没有发起请求,而使用replaceStatus来 mock状态码则不同,是请求响应后再修改状态码。如果error.config 为 undefined,永远不会走到重试,重试的前提必须是 error.config 为之前请求的config,error.config 为 undefined的情况一般为请求没有正常发起

mock method 请求错误情况 重试次数 mock结果 Status error.config error.message
block request 在浏览器block掉请求 1 重试1次 (blocked:devtools) object Network Error
timeout 使用resDelay mock接口超时(whistle) 0 (canceled) object timeout of 8000ms exceeded
statusCode 使用statusCode mock 状态码(whistle)

请求会直接根据设置的状态码返回,不会发起

4xx 无法重试 404

Not Found

403

Forbidden

401

Unauthorized
undefined Unexpected end of JSON input
5xx 无法重试 501

Not Implemented

502

Bad Gateway

503

Service Unavailable
undefined Unexpected end of JSON input
3xx not 304 无法重试 302

Found

303

See Other
undefined Unexpected end of JSON input
200 or 304 无法重试 200

OK

304

Not Modified
undefined Unexpected end of JSON input
replaceStatus 使用replaceStatus mock 接口状态码

replaceStatus是请求响应后再修改状态码,

跟实际比较类似

4xx 1 重试1次 404

Not Found

403

Forbidden

401

Unauthorized
object Request failed with status code 4xx
5xx 1 重试1次 501

Not Implemented

502

Bad Gateway

503

Service Unavailable
object Request failed with status code 5xx
3xx not 304 1 重试1次 302

Found

303

See Other
object Request failed with status code 3xx
状态码200 or 304,code: 1 0 200

OK

304

Not Modified
200: –

304: undefined

200:-

304:Unexpected end of JSON input

状态码200 or 304,code: 非1 0 200

OK

304

Not Modified
200:-

304:Unexpected end of JSON input

正常请求 状态码200,code:1 0 200

OK
状态码200,code: 非1 0 200

OK
跨域 把请求的域 绑定到127.0.0.1,不设跨域 0 (failed)

net::ERR_FAILED
object Network Error
else 把请求的域 绑定到127.0.0.1,设置跨域 0 502

Bad Gateway
undefined Unexpected token < in JSON at ..

 

重试次数和重试延迟

设计重试必须要做一个次数限制,不做次数限制,程序必然会循环重试,如果一直不成功,恭喜你死循环了。

当然为了保险起见,还可以加一个开关控制。一旦发现重试的请求量比较大,可以立即关闭开关。

下面时设置重试次数和发起重试的详细代码:
 apiRetryConfig 为重试脚本配置的一些开关,包括是否重试,重试次数,重试延迟时间等。

apiRetryConfig = {times:1, retry:true, delayTime:200}

export const interceptorsResponse = (apiRetryConfig) =&amp;amp;gt; {
    axios.interceptors.response.use(respose =&amp;amp;gt; {
        // do something
        const { data, status } = respose
        if(status===200) { retrun data }

    }, error =&amp;amp;gt; {
        let config = error.config
        /// Network Error
        if (!config || !config.retry) {
            return Promise.reject(error);
        }
        if (!config.shouldRetry || typeof config.shouldRetry !== 'function') {
            return Promise.reject(error)
        }
        let switchConfig = {
            retry: ~~apiRetryConfig.times,
            shouldRetry: () =&amp;amp;gt; {
                return ~~apiRetryConfig.retry || false
            },
            retryDelay: ~~apiRetryConfig.delayTime || 1
        }
        Object.assign(config, switchConfig)
         
        if (error.message.indexOf('timeout') &amp;amp;gt; -1) {
            // do something
             
        } else {
            
            // 判断是否满足重试条件
            if (!config.shouldRetry(error)) return Promise.reject(error)
            // 设置重试次数
            config.__retryCount = config.__retryCount || 0
            if (config.__retryCount &amp;amp;gt;= config.retry) {
                 
                return Promise.reject(error)
            }
            // 重试次数自增
            config.__retryCount += 1
            // 延时处理重试
            const backoff = new Promise(function (resolve) {
                setTimeout(function () {
                    resolve()
                }, config.retryDelay || 1)
            })
            config.data = qs.parse(config.data)
            // 重新发起请求
            return backoff.then(() =&amp;amp;gt; {
                return axios(config)
            })
        }
    })
}
日志监控

请求过程中可以适当上报一些日志,对请求的各环境进行监控,可以通过日志查询http请求的性能是否健康。

根据需要可以在如下节点进行日志上报:

  • error.config 异常,无法发起重试
  • 埋点上报的请求进入重试流程被拦截
  • shouldRetry 返回 false,无法重试
  • timeout 请求超时上报
  • 满足条件即将进入重试时
  • 成功进入重试,即将发起重试
  • 重试请求响应成功

通过上面的一些节点的统计数据,可以计算某段时间内某个请求的重试成功率

重试成功率 =重试请求响应成功数/总重试次数。

总重试次数 = 无法发起重试 + 重试被拦截的数 + 即将发起重试的数量。

总结

目前只是在axios的基础上进行封装,扩展请求重试功能。如果不依赖http库,需要基于  xhr(XMLHttpRequest) 实现请求重试功能呢?原理应该也是一样,先要基于XHR实现ajax的response拦截器功能。

发表评论

记录工作生活点滴。

返回
顶部