「源码」axios 三大问题解读

本文深入剖析axios源码,解释其如何区分浏览器与服务器请求,实现请求中断,以及请求和响应拦截的内部机制。通过适配器判断环境,CancelToken或AbortController用于取消请求,InterceptorManager管理请求与响应拦截器的执行流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

究竟对 axios 了解多少?这是我这阵一直想要问的问题!之前一直认为这只是一个工具,只要会用就没问题,但是当被一个问题问住的时候,我觉得应该多了解它了,毕竟这是一个公认的,开源的,牛 X 的第三方工具。

Axios 为什么能区分是浏览器请求还是服务器请求

function getDefaultAdapter() {
  var adapter;
  if (typeof XMLHttpRequest !== "undefined") {
    // For browsers use XHR adapter
    adapter = require("../adapters/xhr");
  } else if (
    typeof process !== "undefined" &&
    Object.prototype.toString.call(process) === "[object process]"
  ) {
    // For node use HTTP adapter
    adapter = require("../adapters/http");
  }
  return adapter;
} 

Axios 内部封装了一个适配器,判断是否有 XMLHttpRequest,如果存在则调用 xhr,如果不是则判断 process 是否存在,是否为 Node 环境。

可以查看 getDefaultAdapter 函数用在了哪里

var defaults = {

  transitional: transitionalDefaults,

  adapter: getDefaultAdapter(),
  ...
}
module.exports = defaults; 

作为默认值输出。

查看 defaults.adapter 用在哪里

**
 * Dispatch a request to the server using the configured adapter.
 *
 * @param {object} config The config that is to be used for the request
 * @returns {Promise} The Promise to be fulfilled
 */
module.exports = function dispatchRequest(config) {
  ...

  var adapter = config.adapter || defaults.adapter;

  return adapter(config).then(function onAdapterResolution(response) {
    throwIfCancellationRequested(config);

    // Transform response data
    response.data = transformData.call(
      config,
      response.data,
      response.headers,
      config.transformResponse
    );

    return response;
  }, function onAdapterRejection(reason) {
    if (!isCancel(reason)) {
      throwIfCancellationRequested(config);

      // Transform response data
      if (reason && reason.response) {
        reason.response.data = transformData.call(
          config,
          reason.response.data,
          reason.response.headers,
          config.transformResponse
        );
      }
    }

    return Promise.reject(reason);
  });
}; 

查看 dispatchRequest

/**
 * Dispatch a request
 *
 * @param {Object} config The config specific for this request (merged with this.defaults)
 */
Axios.prototype.request = function request(configOrUrl, config) {
  /*eslint no-param-reassign:0*/
  // Allow for axios('example/url'[, config]) a la fetch API
  if (typeof configOrUrl === 'string') {
    config = config || {};
    config.url = configOrUrl;
  } else {
    config = configOrUrl || {};
  }

  config = mergeConfig(this.defaults, config);

  // Set config.method
  if (config.method) {
    config.method = config.method.toLowerCase();
  } else if (this.defaults.method) {
    config.method = this.defaults.method.toLowerCase();
  } else {
    config.method = 'get';
  }

  ...

  var newConfig = config;
  ...

  try {
    promise = dispatchRequest(newConfig);
  } catch (error) {
    return Promise.reject(error);
  }

  ...

  return promise;
}; 

可以看出最后 getDefaultAdapter 用在了 Axios 的 request 方法上,即请求时,就会调用这个适配器,判断请求环境。并且可以看出 request 方法接收两个参数,一个是 配置项或者 url,另一个是配置项,如果第一个参数是字符串就当作 url 传入,如果是其他类型就和默认配置项合并成一个新的对象。

Axios 为什么能中断请求

官网给出取消请求的方式:

  1. 可以使用 CancelToken.source 工厂方法创建 cancel token,像这样:
const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios
  .get("/user/12345", {
    cancelToken: source.token,
  })
  .catch(function (thrown) {
    if (axios.isCancel(thrown)) {
      console.log("Request canceled", thrown.message);
    } else {
      // 处理错误
    }
  });

axios.post(
  "/user/12345",
  {
    name: "new name",
  },
  {
    cancelToken: source.token,
  }
);

// 取消请求(message 参数是可选的)
source.cancel("Operation canceled by the user."); 
  1. 还可以通过传递一个 executor 函数到 CancelToken 的构造函数来创建 cancel token:
const CancelToken = axios.CancelToken;
let cancel;

axios.get("/user/12345", {
  cancelToken: new CancelToken(function executor(c) {
    // executor 函数接收一个 cancel 函数作为参数
    cancel = c;
  }),
});

// cancel the request
cancel(); 

注意: 可以使用同一个 cancel token 取消多个请求

  1. 从 v0.22.0 Axios 支持 AbortController 取消请求获取 API 的方式:
const controller = new AbortController();

axios
  .get("/foo/bar", {
    signal: controller.signal,
  })
  .then(function (response) {
    //...
  });
// cancel the request
controller.abort(); 

在 github 上可以看到,最新的 readme 可以看到,前两种形式即 CancelToken 方式已经不太推荐。主要原因是可以使用 web 原生自带的 AbortController 类。

在源码中两个位置查看这块取消

  1. 查看 CancelToken 这个类
function CancelToken(executor) {
  if (typeof executor !== "function") {
    throw new TypeError("executor must be a function.");
  }

  var resolvePromise;

  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });

  var token = this;

  // eslint-disable-next-line func-names
  this.promise.then(function (cancel) {
    if (!token._listeners) return;

    var i;
    var l = token._listeners.length;

    for (i = 0; i < l; i++) {
      token._listeners[i](cancel);
    }
    token._listeners = null;
  });

  // eslint-disable-next-line func-names
  this.promise.then = function (onfulfilled) {
    var _resolve;
    // eslint-disable-next-line func-names
    var promise = new Promise(function (resolve) {
      token.subscribe(resolve);
      _resolve = resolve;
    }).then(onfulfilled);

    promise.cancel = function reject() {
      token.unsubscribe(_resolve);
    };

    return promise;
  };

  executor(function cancel(message) {
    if (token.reason) {
      // Cancellation has already been requested
      return;
    }

    token.reason = new CanceledError(message);
    resolvePromise(token.reason);
  });
} 
  1. 关键代码,在 xhr.js 文件中
if (config.cancelToken || config.signal) {
  // Handle cancellation
  // eslint-disable-next-line func-names
  onCanceled = function (cancel) {
    if (!request) {
      return;
    }
    reject(!cancel || (cancel && cancel.type) ? new CanceledError() : cancel);
    request.abort();
    request = null;
  };

  config.cancelToken && config.cancelToken.subscribe(onCanceled);
  if (config.signal) {
    config.signal.aborted
      ? onCanceled()
      : config.signal.addEventListener("abort", onCanceled);
  }
} 

从源码上可以看出,取消请求主要是调用 XMLHttpRequest 的 abort 方法,当使用 signal 方法进行取消时,判断请求是否已经取消。

Axios 是如何实现请求拦截和响应拦截的

官网给出拦截器的用法

在请求或响应被 then 或 catch 处理前拦截它们。

// 添加请求拦截器
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);
  }
); 

如果你想在稍后移除拦截器,可以这样:

const myInterceptor = axios.interceptors.request.use(function () {
  /*...*/
});
axios.interceptors.request.eject(myInterceptor); 

可以为自定义 axios 实例添加拦截器

const instance = axios.create();
instance.interceptors.request.use(function () {
  /*...*/
}); 

拦截器的作用是,在请求前后实现一些公共功能,如在请求前实现一些检验,在请求后实现接口统一的错误拦截。

在源码中查看如何注册拦截器

在 Axios.js 中可以看到下列代码:

function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager(),
  };
} 

我们更深层次挖掘一下,查看 InterceptorManager 类:

function InterceptorManager() {
  this.handlers = [];
}

/**
 * Add a new interceptor to the stack
 *
 * @param {Function} fulfilled The function to handle `then` for a `Promise`
 * @param {Function} rejected The function to handle `reject` for a `Promise`
 *
 * @return {Number} An ID used to remove interceptor later
 */
InterceptorManager.prototype.use = function use(fulfilled, rejected, options) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected,
    synchronous: options ? options.synchronous : false,
    runWhen: options ? options.runWhen : null,
  });
  return this.handlers.length - 1;
};

/**
 * Remove an interceptor from the stack
 *
 * @param {Number} id The ID that was returned by `use`
 */
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null;
  }
};

/**
 * Iterate over all the registered interceptors
 *
 * This method is particularly useful for skipping over any
 * interceptors that may have become `null` calling `eject`.
 *
 * @param {Function} fn The function to call for each interceptor
 */
InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
}; 

注册拦截器,当使用 use 方法时,将拦截器按 fulfilled, rejected, options 封装成一个对象,按顺序 push 在各自 InterceptorManager 的 handlers 中。

如何执行拦截器

  1. 当设置请求拦截器为同步时
if (!synchronousRequestInterceptors) {
  var chain = [dispatchRequest, undefined];

  Array.prototype.unshift.apply(chain, requestInterceptorChain);
  chain = chain.concat(responseInterceptorChain);

  promise = Promise.resolve(config);
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
} 
  1. 执行请求拦截器
while (requestInterceptorChain.length) {
  var onFulfilled = requestInterceptorChain.shift();
  var onRejected = requestInterceptorChain.shift();
  try {
    newConfig = onFulfilled(newConfig);
  } catch (error) {
    onRejected(error);
    break;
  }
} 
  1. 执行响应拦截器
while (responseInterceptorChain.length) {
  promise = promise.then(
    responseInterceptorChain.shift(),
    responseInterceptorChain.shift()
  );
} 

从源码中可以看出来,只要相应的数组长度不为 0,就会一直执行下去,直到最后一个相应拦截器执行完毕,返回的依旧是一个 promise。

注意:请求响应拦截器一定要有返回值,不然结束后无法获取最终的结果

总结

  1. 通过判断是否有 XMLHttpRequest 和 process 判断当前请求是处在浏览器环境还是 node 环境。
  2. 通过 XMLHttpRequest 对象的 abort 方法取消请求操作,在 v0.22.0 之后可以用 AbortController 来取消。
  3. 拦截器通过 InterceptorManager 的 use 方法进行保存在 handlers 中,通过 请求拦截器 -> 分发http请求 -> 响应拦截器 顺序执行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值