axios 源码学习

目录结构

  • catalog

其中,lib/adapters 是具体发起请求的对象,分为两个文件 http.js,xhr.js;http 适用于 node.Js 环境; xhr.js 适用于 浏览器环境;

xhr.js 采用 XMLHttpRequest 对象创建,整体遵循 这个文件中的四步;

module.exports = function bind(fn, thisArg) {
  return function wrap() {
    console.log('arguments--===');
    console.log(arguments);
    var args = new Array(arguments.length);
    for (var i = 0; i < args.length; i++) {
      args[i] = arguments[i];
    }
    return fn.apply(thisArg, args);
  };
};

这个函数的作用是 用来,改变 fn 的执行的作用域的;

无效写法

import Vue from "vue";
import axios from "axios";

axios.defaults.timeout = 5000; //响应时间
axios.defaults.headers.post["Content-Type"] =
  "application/x-www-form-urlencoded;charset=UTF-8";
axios.defaults.baseURL = process.env.VUE_APP_BASE_URL; //配置接口地址
// 添加响应拦截器
axios.interceptors.response.use(
  function (res) {
    return res;
  },
  function (error) {
    // 对响应错误做点什么
    const err = error.response.data;
    if (err.status_code === 417) {
      alert(err.message);
    } else if (err.status_code === 422) {
      for (const item in err.errors) {
        alert(err.errors[item][0]);
      }
    } else {
      alert(err.message ? err.message : "提交失败");
    }
    return Promise.resolve(error.response);
  }
);

const instance = axios.create({
  baseURL: process.env.VUE_APP_BASE_URL
})
const socialInstance = axios.create({
  baseURL: process.env.VUE_APP_SOCIAL_BASE_URL
})

export default axios;

Vue.prototype.$axios = instance;
Vue.prototype.$socailAxios = socialInstance;

怎样优化,怎能实现上述的写法呢?

待解决

给 axios 更改参数的几种形式?

本质都是修改 axios 实例的 defaults 属性内容,而实现方式都是通过 Axios.prototype.request来实现的;

  • 通过实例 defaults 属性

    axios.defaults.timeout=10000;
  • 通过直接给实例传参

    axios({
      timeout:10000
    })
  • 通过 request 来配置

    axios.request('/getUser',{
      timeout:10000
    })
  • 通过 method 别名配置

    axios.get('/getUser',{
      timeout:10000
    })

axios.create() 本身 是返回一个函数,怎样给这个函数 添加 诸如 interceptors 等属性?

通过如下

  var context = new Axios(defaultConfig);
  var instance = bind(Axios.prototype.request, context);

  // Copy axios.prototype to instance
  utils.extend(instance, Axios.prototype, context);
  utils.extend(instance, context);

  return instance;

拦截器怎样触发的呢?

通过 promise 链

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);
});

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

return promise;

通过上述代码,实现了类似如下效果:

Promise.resolve(config).then(interceptors.request).then(dispatchRequest).then(interceptors.response).then(...)

cancelToken 怎样实现 同一个 cancel token 取消多个请求的?

console.log("start");
const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios({
  method: "get",
  url: "/user?ID=12345",
  cancelToken: source.token
}).then(function(response) {
  console.log(response);
});

axios({
  method: "get",
  url: "/country?ID=12345",
  cancelToken: source.token
}).then(function(response) {
  console.log(response);
});

console.log("end--prepare to cancel");
source.cancel("Operation canceled by the user.");

要明白这个问题,必须对 JS 的运行机制非常清楚,诸如 宏任务、微任务的概念,具体看这里;

  • 这段代码作为宏任务,进入主线程。
  • 先遇到 axios cancelToken,生成 cancelToken 对象。
  • 然后遇到第一个 axios 请求,执行合并参数、Axios.prototype.request,最终执行 dispatchRequest,生成 promise 链,完成注册。

    Promise.resolve(config).then(interceptors.request).then(dispatchRequest).then(interceptors.response).then(...)
  • 然后遇到第二个 axios 请求,操作同上。
  • 接下来执行 source.cancel()cancelToken 实例中 reason 属性被赋值;

    if (token.reason) {
      // Cancellation has already been requested
      return;
    }
    
    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  • 好啦,整体代码 script 作为第一个宏任务执行结束;开始 promise.then 微任务,执行刚才生成的两个 promise 链;没有设置 interceptor,直接到 dispatchRequest
  • 执行throwIfCancellationRequested

    function dispatchRequest(config) {
      throwIfCancellationRequested(config);
    }
    
    function throwIfCancellationRequested(config) {
      if (config.cancelToken) {
        config.cancelToken.throwIfRequested();
      }
    }
  • 执行 cancelToken 原型中 throwIfRequested,因为 cancelToken 实例 reason 属性已经被赋值,所以请求取消;同时,由于两个请求使用的是同一个 cancelToken 实例,所以可以取消多个 http 请求。

    CancelToken.prototype.throwIfRequested = function throwIfRequested() {
      if (this.reason) {
        throw this.reason;
      }
    };

通过 cancelToken 这块的代码,对于面向对象编程理解深了一步;各个文件中间,通过暴露的接口进行 通信(比如 xhr.js 和 axios 各种配置的通信);面向对象可以更好的解释这个通信过程;而如果使用 面向过程的编程方式,估计会乱成一锅粥


前端熟练工
1.8k 声望66 粉丝

要做前端架构师的正在前行的人