axios是什么,无需多讲,axios解析的可以看下77.9K Star 的 Axios 项目有哪些值得借鉴的地方这篇文章
为什么需要请求重试
项目中,经常会有很多用户的网络抽风或者各种原因造成偶发性的网络异常请求错误,如果没有重试机制,有时候体验就比较糟糕。这个时候实现网络错误请求错误重试也能比较好的解决这种偶发场景。
如何去做呢
我们可以使用axios-retry
这个库去实现重拾。用法也非常简单
import axiosRetry from 'axios-retry';
axiosRetry(axios, {});
直接执行axiosRetry
传递axios实例即可。同时它会支持几个配置参数
retries
: 重试次数,默认是3次retryCondition
:一个函数判断发生错误时是否重试。默认是5xx
http 错误或者网络异常或者是幂等请求(GET/HEAD/ OPTIONS/PUT/DELETE)才会重试。shouldResetTimeout
:重试的时候是否重置超时时间。默认不重置。也就是说多次重试请求必须在timeout
内结束retryDelay
每个请求之间的重试延迟时间,默认为0
例如,如果我想定制,重试4次、除了默认情况重试外,404也重试、重置超时时间、重试延迟时间50ms,则这样即可
import axiosRetry from 'axios-retry';
axiosRetry(axios, {
retries: 4,
retryCondition: (err) => axiosRetry.isNetworkOrIdempotentRequestError(err) || error.response.status === 404,
shouldResetTimeout: true,
retryDelay: 50
});
实现原理
axios-retry
实现重试的原理也比较简单
axios-retry
会在axios的config的axios-retry
字段中保存当前已经重试的次数(retryCount
)- axios会在http异常/网络异常的情况下抛出错误。
axios-retry
则在响应拦截器中注册错误处理函数,执行retryCondition
判断是否需要进行重试。如果需要重试则对retryCount
进行++操作,然后返回一个Prommise使用当前的config重新发起一次新的请求new Promise(resolve => setTimeout(() => resolve(axios(config)), delay));
。如果当前不需要重试(retryCondition
返回false
或者已经超过重试次数的场景,直接reject这个错误对象)
axios.interceptors.response.use(null, error => {
const config = error.config;
// ....
const currentState = getCurrentState(config);
const shouldRetry = retryCondition(error) && currentState.retryCount < retries;
if (shouldRetry) {
currentState.retryCount += 1;
//.....
return new Promise(resolve => setTimeout(() => resolve(axios(config)), delay));
}
return Promise.reject(error);
});
详细代码在此。还是非常清晰易懂的
更进一步
在实际场景中,很多时候http请求成功并不说明我们的请求就符合预期的。有以下子几种场景,如果直接使用axios-retry
是无法触发重拾的
-
业务code异常
以笔者实际项目为例,后端返回异常时,http code为200,但是返回code非0的错误,如{code:1,msg:'some err'}
。有的时候可能是一些偶发错误,这个时候可能也需要重试 -
异步接口返回不符合预期
假设以下场景。首先操作a先上传了视频;紧接这b操作去查询这个视频的信息,可能刚上传完,后端一些信息落db的时候有延迟。偶发的我们马上查询的时候可能查不到这个信息,需要延迟个几ms才能查到。例如查不到返回{code:0,data:null}
查到返回code:0,data:'some thing'
。这个时候重试也是很重要了。
如何优雅重试
上文提到axios-retry
的重试原理是通过响应拦截器的错误处理函数去实现的,那么我们在响应拦截器的正常处理函数中抛出这个这个错误是否可以呢?当然是可以的。
- 给
axios
的config加一个自定义选项函数判断是否需要重试 - 在响应拦截器中调用判断函数,若需要重试,设置一个标志位,
Promise.reject
抛出一个错误
instance.interceptors.response.use((response) => {
const { data, config, request } = response
if (config?.[namespace]?.shouldRetry?.(data)) {
config[namespace].needRetry = true
return Promise.reject(
createError(
`Axios retry enhance error`,
config,
null,
request,
response
)
)
}
return response
})
axios-retry
的retryCondition
读取到上一步的属性返回true,即可利用axios-retry进行重试
axiosRetry(instance, {
...config,
retryCondition: (error) => {
const {
retryCondition = axiosRetry.isNetworkOrIdempotentRequestError,
} = config
return retryCondition(error) || error.config?.[namespace]?.needRetry
},
})
于是,代码调用的时候只需如下即可
client.get<Result>('http://example.com/test', {
retry: {
// The request will retry when the code isn't 0 even the http code is 200
shouldRetry: (res: Result) => res.code !== 0,
},
})
封装
综合以上讨论,针对axios-retry
进行了二次封装,实现了axios-retry-enhancer。支持axios-retry
原来的参数,并且额外支持上面提到的定义重试逻辑。用法如下即可实现业务优雅重试
import axiosRetryEnhancer from 'axios-retry-enhancer'
import axios from 'axios'
const client = axios.create()
axiosRetryEnhancer(client, {
// same options with axios-retry. See https://github.com/softonic/axios-retry#options
})
interface Result<T = unknown> {
code: number
data: T
}
client.get<Result>('http://example.com/test', {
retry: {
// The request will retry when the code isn't 0 even the http code is 200
shouldRetry: (res: Result) => res.code !== 0,
},
})
码字不易,你的点赞是我最大的动力,嘿嘿