axios竞态问题处理

556 阅读2分钟

背景:

request的封装通常是一个项目中不可或缺的部分,不管是基于xhr,fetch,rxjs,axios... 关于竞态上的处理是一个很值得考虑的方面,看了网上一些处理方法,大致有:

防抖、设置loading或锁状态、中断之前的请求。但一个系统里面,业务场景多种多样,不能说用哪一种能解决所有场景,只能按需使用,个人做法是防抖和锁状态在业务代码里处理,中断请求通过request的封装扩展字段处理,因为也不是所有接口都一刀切用中断请求,所以需要通过配置来适配同一接口是否需要中断,一般来说像表格数据接口,详情数据接口等适合中断请求的设定。这里就主要介绍axios的中断请求扩展。

关于AbortController

AbortController

AbortController 接口表示一个控制器对象,允许你根据需要中止一个或多个 Web 请求。

构造函数

AbortController.AbortController()

创建一个新的 AbortController 对象实例。

属性

AbortController.signal 只读

返回一个 AbortSignal 对象实例,可以用它与一个 DOM 请求进行通信或者中止该请求。

方法

AbortController.abort()

中止一个尚未完成的 Web(网络)请求。这能够中止 fetch 请求及任何响应体的消费和流。

思路:为每个配置了isSignal:true的请求创建一个 AbortController 实例,并将其添加到请求配置中,以便我们可以随时取消请求。 

requestInterceptors设置:
request.isSignal && axiosCanceler.addPending(response);

responseInterceptors设置: response.config.isSignal && axiosCanceler.removePending(response.config);

关于axios的封装:

import { getLocale, history } from '@umijs/max';
import { message } from 'antd';
import { first, get, set } from 'lodash';
import * as NProgress from 'nprogress';
import queryString from 'query-string';
import { AxiosCanceler } from './axiosCancel';
import { EN_LANG } from './locales';
import translate from './locales/translate';
import { storage } from './utils/tzStorage';
NProgress.configure({
  easing: 'linear',
  speed: 350,
  showSpinner: false,
});
const codeMessage: Record<number, string> = {
  200: translate('request.code.200'),
  201: translate('request.code.201'),
  202: translate('request.code.202'),
  204: translate('request.code.204'),
  400: translate('request.code.400'),
  401: translate('request.code.401'),
  403: translate('request.code.403'),
  404: translate('request.code.404'),
  406: translate('request.code.406'),
  410: translate('request.code.410'),
  422: translate('request.code.422'),
  500: translate('request.code.500'),
  502: translate('request.code.502'),
  503: translate('request.code.503'),
  504: translate('request.code.504'),
};


const axiosCanceler = new AxiosCanceler();


const errorHandler = (error: any) => {
  const { response } = error ?? {};
  if (response?.status) {
    const errorText = codeMessage[response.status] || response.statusText;
    const { status } = response;
    const errorTxt = `${(response?.data as any)?.message || errorText}`;
    if (typeof response?.data !== 'string') {
      if ([401, 503].includes(status)) {
        storage.remove('userInfo');
        storage.clearCookie();
      }
      status === 401 && history.replace('/login');
      status === 503 && history.replace('/503');


      message.error({
        key: status === 401 ? status : +new Date(),
        content: errorTxt,
      });
      return response;
    }
    message.error({
      key: first(`${response.status}`) === '5' ? status : +new Date(),
      content: errorTxt,
    });
  } else if (!response) {
    // console.log(response);
    // message.error(translate('request.errorTip'));
  }
  return response;
};
const requestInterceptors = (request: {
  isSignal?: boolean;
  headers: { Authorization: string };
}) => {
  request.isSignal && axiosCanceler.addPending(request);
  const token = storage.getCookie('token');
  const lang = getLocale();
  if (token) request.headers.Authorization = `Bearer ${token}`;
  const contentType = get(request, ['headers', 'Content-Type']);
  !contentType && set(request, ['headers', 'Content-Type'], 'application/json');
  set(request, ['headers', 'Accept-Language'], lang === EN_LANG ? 'en' : 'zh');
  NProgress.start();
  return request;
};
const responseInterceptors = (response: any) => {
  response.config.isSignal && axiosCanceler.removePending(response.config);


  NProgress.done();
  const {
    data,
    config: { customHandleRes },
  } = response;


  if (customHandleRes) {
    return response;
  }
  const { code } = data;
  if (!code) {
    return response.data;
  }
  return Promise.reject(response);
};


export const requestConfig = {
  paramsSerializer(params: any) {
    return queryString.stringify(params);
  },
  errorConfig: {
    errorHandler,
  },
  timeout: 100 * 1000,
  baseURL: process.env.PUBLIC_URL,
  // 请求拦截器
  requestInterceptors: [requestInterceptors],


  // 响应拦截器
  responseInterceptors: [
    [
      responseInterceptors,
      (error: any) => {
        const { config, response } = error;
        NProgress.done();


        if (!config?.skipErrorHandler) {
          errorHandler(error);
        }
        return Promise.reject(response?.data);
      },
    ],
  ],
};

AxiosCanceler类将是一个非常有用的工具。它可以让你更好地控制你的异步请求,并避免一些不必要的错误。完整代码示例:celeris-admin/packages/web/request/src at master · kirklin/celeris-admin (github.com)

参考文章:

如何使用 Axios 的 AbortController 取消请求