加载中...
Axios源码学习
发表于:2022-05-30 | 分类: 前端

axios 中统一的封装了如下的问题

  • 支持浏览器请求 XMLHttpRequests 请求
  • 支持 node 中的 http 模块的请求 http 请求
  • 支持 Promise API
  • 可以拦截请求和响应
  • 转换 request 和 response 数据
  • 取消请求
  • 自动转换 JSON 数据
  • 默认增加解决 XSRF(CSRF)攻击的参数 XSRF
  1. axios 中是如何实现在浏览器和 node 中发起 http 请求的?且支持 Promise 调用

在 axios 中,针对浏览器和 node 环境中,分别通过 Promise 配合 XMLHttpRequest 和 http 模块,来实现了两个不同环境下的 adapter,具体代码如下:

function getDefaultAdapter() {
  // 判断当前是在浏览器环境还是node环境
  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;
}
  1. 它是如何拦截请求的?

当你通过 axios 发起请求时,且注册了request interceptors 或者 response interceptors后,axios 会在初始化阶段创建一个var chain = [dispatchRequest, undefined]数组。然后会将注册的request interceptorsresponse interceptors 分别插入到chain的头部和尾部。
最后再通过 promise 顺序执行链式调用。完成请求和响应的拦截功能。

// Hook up interceptors middleware
// 初始化的chain中的dispatchRequest就是执行的http请求
// 添加拦截器中间件
var chain = [dispatchRequest, undefined];
// 初始化一个promise实例
var promise = Promise.resolve(config);

// 将request拦截器添加到chain的头
this.interceptors.request.forEach(function unshiftRequestInterceptors(
  interceptor
) {
  chain.unshift(interceptor.fulfilled, interceptor.rejected);
});

// response拦截器添加到chain的尾部
this.interceptors.response.forEach(function pushResponseInterceptors(
  interceptor
) {
  chain.push(interceptor.fulfilled, interceptor.rejected);
});

// 在向chain的头尾添加拦截器后,按照promise的then,顺序执行
while (chain.length) {
  promise = promise.then(chain.shift(), chain.shift());
}

return promise;
  1. 它是如何取消请求的呢?

首先,我们需要 通过 config.cancelToken = new axios.CancelToken(function executor(c) {}) 去实例化一个 cancelToken, 传入 executor 的 c 则就是 cancel 函数,调用它则会取消当前请求

function CancelToken(executor) {
  if (typeof executor !== "function") {
    throw new TypeError("executor must be a function.");
  }

  var resolvePromise;
  // 创建一个promise
  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });

  var token = this;
  // 执行executor,并传入一个函数,当用户想要cancel掉某个请求时,只需要执行这个传入的函数,并且输入相应的提示信息即可
  executor(function cancel(message) {
    if (token.reason) {
      // Cancellation has already been requested
      return;
    }

    token.reason = new Cancel(message);
    // 执行promise的resolve,传入cancel
    resolvePromise(token.reason);
  });
}

// 创建一个cancel原因
function Cancel(message) {
  this.message = message;
}

如果熟悉XMLHttpRequest的方法,那我们应该知道一个abort。对,在 axios 中同样是通过 abort 来实现的取消请求。在xhr.js中有这么一段代码。
如果实例化了 config.cancelToken,则会执行,并且 reject 一个原因

// 首先需要在request config中配置cancelToken,
if (config.cancelToken) {
  // Handle cancellation
  config.cancelToken.promise.then(function onCanceled(cancel) {
    if (!request) {
      return;
    }

    // 通过abort终止当前请求
    request.abort();
    reject(cancel);
    // Clean up request
    request = null;
  });
}

实际应用中,我们可以通过 cancelToken 来记录请求 url,以避免重复请求。并且可创建一个白名单,以防止一些需要多次请求的接口

import axios from "axios";

const whiteList = ["/api/1", "/api/2"]; //这些接口不进行 防重复提交

const requestMap = new Map();
const CancelToken = axios.CancelToken;
// 防止重复请求
const removeRequest = (mapkey) => {
  const cancel = requestMap.get(mapkey);
  cancel("请勿重复操作");
  requestMap.delete(mapkey);
};

// 过滤请求
axios.interceptors.request.use(
  (config) => {
    /** 其他的配置 **/

    // 使用的是请求方法和请求url组成的key
    const mapkey = `${config.method}-${config.url}`;
    if (requestMap.has(mapkey)) {
      // 上删除上一次未完成的请求
      removeRequest(mapkey);
    }
    if (!requestMap.has(mapkey)) {
      config.cancelToken = new CancelToken(function executor(c) {
        // An executor function receives a cancel function as a parameter
        if (config.url && whiteList.indexOf(config.url) === -1) {
          requestMap.set(mapkey, c);
        }
      });
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// 添加响应拦截器
axios.interceptors.response.use(
  (response) => {
    const mapkey = `${response.config.method}-${response.config.url}`;
    // 在一个ajax响应后再执行一下取消操作
    if (requestMap.has(mapkey)) {
      removeRequest(mapkey);
    }
  },
  (error) => {
    // 在一个ajax响应后再执行一下取消操作
    const mapkey = `${error.config.method}-${error.config.url}`;
    if (requestMap.has(mapkey)) {
      removeRequest(mapkey);
    }
    return Promise.reject(error);
  }
);

// 添加响应拦截器
axios.interceptors.response.use(
  (response) => {
    /* 一些你自己的配置 */
    removePending(response.config, "response"); // 在一个ajax响应后再执行一下取消操作,把已经完成的请求从pending中移除  下次请求同样的url就不会执行
  },
  (error) => {
    /* 一些你自己的配置 */
    return Promise.reject(error);
  }
);
  1. axios 中如何解决的 XSRF(CSRF)安全问题?
    defaults.js中,有一些默认的 config 配置, 这需要后端来协同。具体文章可查看
{
  xsrfCookieName: 'XSRF-TOKEN', // 默认值, 表示如果需要防止XSRF攻击,则需要后端生成一个key为XSRF-TOKEN的session(当然,也可以和后端协商,然后自己写config来覆盖)
  xsrfHeaderName: 'X-XSRF-TOKEN', // 然后在axios中自动的读取cookie中的值并在header中写入一个自定义的头X-XSRF-TOKEN
}

读取的操作在xhr.js

if (utils.isStandardBrowserEnv()) {
  var cookies = require("./../helpers/cookies");

  // Add xsrf header
  // (当配置了允许访问cookie设置 || 访问该请求在同一个源 ) && config.xsrfCookieName
  var xsrfValue =
    (config.withCredentials || isURLSameOrigin(fullPath)) &&
    config.xsrfCookieName
      ? cookies.read(config.xsrfCookieName)
      : undefined;

  if (xsrfValue) {
    requestHeaders[config.xsrfHeaderName] = xsrfValue;
  }
}
上一篇:
CommonJs和Es Module及它们的区别
下一篇:
Proxy构造函数
本文目录
本文目录