看源码,躺平很重要 —— Axios源码解读

为何要阅读源码?用尤大的话:提升自己的行业竞争力。本文以Axios为例,带你一下学习源码。之所以选择Axios,因为它是一款非常流行的处理http请求的库,前端几乎人人在用,复杂度适中且有很好的注释解释。

姿势很重要

正式开始前,先普及一下源码阅读的正确姿势:

  • Github fork一个你想学习的项目(如果你不知道Github,那你不用往下看了)
  • 在fork的项目下建一个学习分支
  • 熟悉、理解项目结构
  • 开始阅读(最好遵循一定的顺序)
  • 在源代码里添加你自己的理解或标记
  • 提交学习记录到学习分支

再有,就是要有一个平常的心态,没有人天生就是代码高手;同时要有正确的学习理念,那就是:

  • 你是学习人家的设计思想,而不是全盘吸收,这玩意真不值得你全部背下来
  • 对于那些非常巧妙的点,一定要重点关注,确保理解和吸收。
  • 做一些练习巩固你的理解

好,你准备好了吗?进入正题!

先别慌,瞄一眼很重要

先看他的package.json文件,了解两个关键信息:

  • 入口文件是index.js
  • 打包命令是npm run build

image.png

打开terminal键入命令npm installnpm run build

然后进入dist目录查看axios.js文件:

image.png
这是最后一行的行号–2297,其实我想说非压缩的版本只有2000多行,所以确实不复杂,我没骗你的!

其次再瞄一眼目录结构:

image.png

lib目录是存放源码的,像其他目录如 examplessandboxtest等是起辅助作用的,dist是编译包的存放目录,所以我们主要关注的部分将是lib目录。

ok,瞄完了!
所以我们知道了应该从入口index.js开始,源码都在lib目录,不懂可以看注释文档或README.md

从入口文件开始

入口文件内容很直接:module.exports = require('./lib/axios');,所以间接入口是lib目录下的axios.js
内容也很简单:

  • 创建一个Axios的实例
  • 往上面挂几个方法或属性
  • 返回该实例
'use strict';
//引入省略。。。

/**
 * Create an instance of Axios
 *
 * @param {Object} defaultConfig The default config for the instance
 * @return {Axios} A new instance of Axios
 */
 
// 创建Axios的一个实例的工厂函数
function createInstance(defaultConfig) {  
  //省略了
}

// 执行示例创建
// Create the default instance to be exported
var axios = createInstance(defaults);

// 暴露自己的爹
axios.Axios = Axios;

// 把创建自己的工厂函数也暴露出去
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

// 挂载几个有用的方法
// 省略。。。
axios.xxx = yyyy;

// 最后导出,o了
module.exports = axios;

// Allow use of default import syntax in TypeScript
module.exports.default = axios;
复制代码

现在,你明白了Axios实例是通过createInstance()创建的,可以通过axios.create()间接调用它,并且该库的作者非常贴心,默认为你创建好并导出了,你直接 require('axios')就可以使用了。

这样阅读是不是有点走马观花?我觉得一点也不,这里不值得我们下狠功夫!

请求是如何发起的?

我们知道,可以通过调用axios.get|post发起请求,然后处理返回的promise对象即可对响应的数据做处理。那么,这一切的背后原理是怎样的?

首先这些方法在哪里定义?Axios原型对象:

// lib/core/Axios.js
// 遍历方法数组给原型对象添加方法
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, config) {
    return this.request(mergeConfig(config || {}, {
      method: method,
      url: url,
      data: (config || {}).data
    }));
  };
});

// 遍历方法数组给原型对象添加方法
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, data, config) {
    return this.request(mergeConfig(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});
复制代码

接着我们看到会返回this.request函数的调用返回值,它当然是一个promise对象:

/**
 * Dispatch a request
 *
 * @param {Object} config The config specific for this request (merged with this.defaults)
 */
Axios.prototype.request = function request(config) {

  // 省略了无数行,这里他们不重要
  
  try {
    //创建请求的promsie对象
    promise = dispatchRequest(newConfig);
  } catch (error) {
    return Promise.reject(error);
  }
  
  // 省略了无数行,这里他们不重要
  
  //返回一个promise对象
  return promise;
};
复制代码

所以我们断定dispatchRequest这哥们是个关键角色:

/**
 * 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) {

  // 省略了无数行  
  
  // 获取adapter
  var adapter = config.adapter || defaults.adapter;
  
  return adapter(config).then(function onAdapterResolution(response) {
  
    // 转换相应数据的,这里不重要,省略
    
    return response;
  }, function onAdapterRejection(reason) {
  
    // 转换相应数据的,这里不重要,省略

    return Promise.reject(reason);
  });
}
复制代码

可以基本断定直接负责请求的就是这个adapter,由于axios是一个可以运行在web和node里的http client,所以这里的adapter也分web和node两种,分别对应的文件是adapters/xhr.jsadapters/http.js:

 // lib/defaults.js
 
 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');
  }
复制代码

所以我们的目光又来到了xhr.js:

function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    var request = new XMLHttpRequest();
    
    function onloadend() {
      if (!request) {
        return;
      }
      // 省略。。。
      
      //将resolve和reject托管给settle函数处理
      settle(resolve, reject, response);

      // Clean up request
      request = null;
    }
    
    if ('onloadend' in request) {
      // Use onloadend if available
      request.onloadend = onloadend;
    } else {
      // Listen for ready state to emulate onloadend
      request.onreadystatechange = function handleLoad() {
        if (!request || request.readyState !== 4) {
          return;
        }
        // 省略了一部分处理边界的代码
        setTimeout(onloadend);
      };
    }
    request.onabort = function(){ 
      // 省略
    }
    request.onerror = function(){ 
      // 省略
    }
    request.onerror = function(){ 
      // 省略
    }
  })

}
复制代码

可以看到在xhr的onload事件处理函数里,promise的resolvereject函数会被传递给settle函数,由它来决定resolvereject的调用。这俩函数如果不被调用,任你怎么axios.get(...).then()都不会得到预期的效果。

相信settle.js应该没几行代码了:

module.exports = function settle(resolve, reject, response) {
 // 对响应做校验,通过则调用resolve 否则reject
  if (!response.status || !validateStatus || validateStatus(response.status)) {
    resolve(response);
  } else {
    reject(createError(
      'Request failed with status code ' + response.status,
      response.config,
      null,
      response.request,
      response
    ));
  }
};
复制代码

好了,流程看完了,再来梳理一遍http请求的执行栈(流程),以get方法为例:

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享