Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

77.9K Star 的 Axios 项目如何优雅实现请求重试 #21

Open
flytam opened this issue Nov 17, 2020 · 0 comments
Open

77.9K Star 的 Axios 项目如何优雅实现请求重试 #21

flytam opened this issue Nov 17, 2020 · 0 comments

Comments

@flytam
Copy link
Owner

flytam commented Nov 17, 2020

axios是什么,无需多讲,axios解析的可以看下77.9K Star 的 Axios 项目有哪些值得借鉴的地方这篇文章

为什么需要请求重试

项目中,经常会有很多用户的网络抽风或者各种原因造成偶发性的网络异常请求错误,如果没有重试机制,有时候体验就比较糟糕。这个时候实现网络错误请求错误重试也能比较好的解决这种偶发场景。

如何去做呢

我们可以使用axios-retry这个库去实现重拾。用法也非常简单

import axiosRetry from 'axios-retry';
axiosRetry(axios, {});

直接执行axiosRetry传递axios实例即可。同时它会支持几个配置参数

  • retries: 重试次数,默认是3次
  • retryCondition:一个函数判断发生错误时是否重试。默认是5xxhttp 错误或者网络异常或者是幂等请求(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-retryretryCondition读取到上一步的属性返回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,
  },
})

码字不易,你的点赞是我最大的动力,嘿嘿

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant