源码解析Axios拦截器执行顺序

1,471 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第9天,点击查看活动详情

在进行axios请求封装的时候需要用到拦截器,使用的时候发现了一个问题是拦截器的执行先后顺序的问题,拦截器的执行顺序和注册顺序之间有一定的关系,并且在使用请求拦截器时,请求拦截器的执行顺序和注册顺序相反,响应拦截器的响应顺序和注册顺序相同。

请求例子

在下面的例子中我在自己封装的axios中使用了全局的一个拦截器,在这里拦截全局的请求和响应,接下来我还会对单个请求做个拦截器

constructor(config: RequestConfig) {
    this.instance = axios.create(config)
    this.cancelRequestSourceList = []
    this.instance.interceptors.request.use(
      (res: AxiosRequestConfig) => {
        //携带token
          res.headers.Authorization = 'token'
        console.log('全局请求拦截器',res.url)
        return res
      },
      (err: any) => err,
    )
    this.instance.interceptors.response.use(
      // 因为我们接口的数据都在res.data下,所以我们直接返回res.data
      (res: AxiosResponse) => {
        console.log('全局响应拦截器')
        return res.data 
      },
      (err: any) => err,
    )
}

单个拦截器

这里设置拦截器的目的是对一些特定的接口做一些数据的拦截,所以这种接口发出的请求会经过俩个拦截器,一个是自己接口设定的拦截器,另一个就是全局的拦截器,

const req = () => {
  return request<Req, Res>({
    url: '/api/m1/1381288-0-default/app/v1/test',
    method: 'GET',
    data:{
    },
    paramsSerializer: (data) => qs.stringify(data, { indices: false }),
    interceptors: {
      requestInterceptors(res) {
        console.log('接口请求拦截',res)
        return res
      },
      responseInterceptors(result) {
        console.log('接口响应拦截',result)
        return result
      },
    },
  })
}

单个拦截器的注册在发送request的时候提前判断,这里具体我就不讲如何实现,我们可以看到全局拦截器的注册是比单例请求的拦截器注册的更早的,全局拦截在constructor中注册,而单例的拦截在request中才注册

request<T>(config: RequestConfig): Promise<T> {
    return new Promise((resolve, reject) => {
      // 如果单个请求有拦截器,拦截后重新给config赋值
      if (config.interceptors?.requestInterceptors) {
        config = config.interceptors.requestInterceptors(config)
      }

请求结果

设置好拦截器后我们发现执行顺序的问题 注册顺序是 全局请求拦截->接口请求拦截->全局响应拦截->接口响应拦截 响应顺序是 接口请求拦截->全局请求拦截->全局响应拦截->接口响应拦截 image.png

源码查询

Axios.prototype.request = function request(config) {
  /*eslint no-param-reassign:0*/
  // Allow for axios('example/url'[, config]) a la fetch API
  if (typeof config === 'string') {
    config = arguments[1] || {};
    config.url = arguments[0];
  } else {
    config = config || {};
  }

  // 合并配置
  config = mergeConfig(this.defaults, config);
  // 添加method配置, 默认为get
  config.method = config.method ? config.method.toLowerCase() : 'get';

  var chain = [dispatchRequest, undefined];
  var promise = Promise.resolve(config);

  // 后添加的请求拦截器保存在数组的前面
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });
  // 后添加的响应拦截器保存在数组的后面
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });
  

  // 通过promise的then()串连起所有的请求拦截器/请求方法/响应拦截器
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  // 返回用来指定我们的onResolved和onRejected的promise
  return promise;
};

这里声明一个chain数组用来保存拦截器,通过俩个forEach来循环放到数组中,这里可以看到请求拦截是用unshift的方法推到数组前面,而响应拦截是推送到数组后,所以最后用.then进行promise调用到时候,执行顺序就会变成下面的样子

image.png

由于请求的发送需要在请求拦截器之后,在响应拦截器之前,所以数组先放入request,接着在数组的前后分别加入请求和响应拦截器,由于加入请求拦截器的方法是unshift,所以最后通过promise进行请求的链式调用的时候,我们可以看到执行顺序是从左往右的,所以最后注册的请求拦截器会最先执行,而响应拦截的执行顺序和注册顺序是一样的。

参考链接## AXIOS拦截器执行顺序的源码解释