axios源码分析及模型axios实现

102 阅读8分钟

一、以下是对axios.jsAxios.jsInterceptorManager.js源码的解读:

axios.js文件:

'use strict';
// axios入口文件
// 引入工具
var utils = require('./utils');
// 引入绑定函数,创建函数
var bind = require('./helpers/bind');
// 引入Axios主文件
var Axios = require('./core/Axios');
// 引入合并配置文件
var mergeConfig = require('./core/mergeConfig');
// 导入默认配置
var defaults = require('./defaults');

/**
 * Create an instance of Axios
 * 创建一个Axios实例对象
 * @param {Object} defaultConfig The default config for the instance
 * @return {Axios} A new instance of Axios
 */
function createInstance(defaultConfig) {
  // 创建一个实例对象 context可以调用get,put,post,delete,request
  var context = new Axios(defaultConfig); // contest不能当函数使用
  // 将request方法的this指向context并返回新函数,instance可以用作函数使用,切返回的是一个promise对象
  var instance = bind(Axios.prototype.request, context);

  // Copy axios.prototype to instance
  // Axios.prototype和实例对象的方法都添加到instance函数身上
  utils.extend(instance, Axios.prototype, context);

  // Copy context to instance
  // 将实例对象的方法和属性扩展到instance身上
  utils.extend(instance, context);

  return instance;
}

// Create the default instance to be exported
// 通过配置创建axios函数
var axios = createInstance(defaults);

// Expose Axios class to allow class inheritance
// axios添加Axios属性,属性值为构造函数对象,axios.CancelToken = CancelToken
axios.Axios = Axios;

// Factory for creating new instances
// 工厂函数,用来返回创建实例对象函数
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

// Expose Cancel & CancelToken
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');

// Expose all/spread
axios.all = function all(promises) {
  return Promise.all(promises);
};
axios.spread = require('./helpers/spread');

// 简单实现全局暴露axios
window.axios = axios

module.exports = axios;

// Allow use of default import syntax in TypeScript
module.exports.default = axios;

Axios.js文件:

'use strict';

var utils = require('./../utils');
var buildURL = require('../helpers/buildURL');
// 引入拦截器管理构造函数
var InterceptorManager = require('./InterceptorManager');
// 引入发送请求的函数
var dispatchRequest = require('./dispatchRequest');
// 获取合并配置的函数
var mergeConfig = require('./mergeConfig');

/**
 * Create a new instance of Axios
 * 创建Axios构造函数
 * @param {Object} instanceConfig The default config for the instance
 */
function Axios(instanceConfig) {
  // 实例对象上的 defaults属性为配置对象
  this.defaults = instanceConfig;
  // 实例对象上有interceptors属性用来设置请求和响应拦截器
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

/**
 * Dispatch a request
 * 发送请求方法,原型上配置,则实例对象就可以调用request方法发送请求
 * @param {Object} config The config specific for this request (merged with this.defaults)
 */
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);

  // 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';
  }

  // Hook up interceptors middleware
  //创建拦截器中间件,第一个参数用来发送请求,第二个为undefined用来补位。
  var chain = [dispatchRequest, undefined];
  //创建一个成功的promise切成功的值为合并后的请求配置
  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);
  });
  // 如果链条长度不为0
  while (chain.length) {
    // 依次取出chain的回调函数,并执行
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

Axios.prototype.getUri = function getUri(config) {
  config = mergeConfig(this.defaults, config);
  return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, '');
};

// Provide aliases for supported request methods
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, data, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});

module.exports = Axios;

注: axios$原理是一样的,它们先造一个函数出来,然后在往函数身上添加方法,然后我们使用的时候,既可以当函数用也可以调方法使用。

InterceptorManager.js文件:

'use strict';
// 拦截器管理器构造函数
var utils = require('./../utils');

/**
 * 声明构造函数
 * axios.interceptors.request.use
 * axios.interceptors.response.use
 */

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) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};

/**
 * Remove an interceptor from the stack
 * 从拦截器数组中移除制定ID的拦截器 
 * @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);
    }
  });
};

module.exports = InterceptorManager;

二、手写实现axios


// 构造函数
function Axios(config){
    // 初始化
    this.defaults = config;
    this.interceptors = {
        request:{},
        response:{}
    }
}

// 原型添加相关方法
Axios.prototype.request = function(config){
    console.log("发送Ajax请求,请求类型为:" + config.method)
}

Axios.prototype.get = function(config){
    return this.request({
        method:'GET'
    })
}

Axios.prototype.post = function(config){
    return this.request({
        method:'POST'
    })
}


// 声明函数 
function createInstance(config){
    // 实例化一个对象
    let context = new Axios(config); // axios.get()...,但是不能当做函数使用,例如context();
    // 创建请求函数
    var instance = Axios.prototype.request.bind(context); // instance是一个函数并且可以instance({}),此时instance不能当做对象使用,例如:instance.get()
    // 将Axios.prototype对象中的方法添加到instance函数对象中
    Object.keys(Axios.prototype).forEach(key=>{
        console.log(key,"key");
        instance[key] = Axios.prototype[key].bind(context); // 可以使用this.default,this.interceptors
    })
    // 为instance函数对象添加default和interceptors
    Object.keys(context).forEach(key=>{
        console.log(key,"key");
        instance[key] = context[key]
    })
    // console.dir(instance);
    return instance;
}

let axios = createInstance();
axios({method:'POST'})
axios.get()
axios.post()

三、模拟axios实现过程

// 1.声明构造函数
function Axios(config){
    this.config = config
}

Axios.prototype.request = function(config){
    // 创建一个promise对象
    let promise = Promise.resolve(config);
    // 声明一个数组
    let chains = [dispatchRequest,undefined];
    // 调用then方法制定回调
    let result = promise.then(chains[0],chains[1]);
    return result;
}

// 2.dispatchRequest函数,返回结果是一个promise对象
// 返回promise对象
function dispatchRequest(config){
    console.log("config",config);
    // 调用适配器发送请求
    return xhrAdapter(config).then(response =>{
        console.log(response);
        // 对响应的结果进行转换处理
        return response; //暂时不做处理
     },error =>{
        console.log(error);
        throw error;
     });
}

// 3.adapter适配器
function xhrAdapter(config){
    console.log("adapter",config);
    return new Promise((resolve,reject)=>{
        // 发送ajax请求
        let xhr = new XMLHttpRequest();
        xhr.open(config.method,config.url);
        xhr.send();
        xhr.onreadystatechange = function(){
            if(xhr.readyState === 4){
                if(xhr.status === 200 && xhr.status < 300){
                    resolve({
                        // 配置对象
                        config:config,
                        // 响应体
                        data:xhr.response,
                        // 响应头
                        headers:xhr.getAllResponseHeaders(),
                        // xhr请求对象
                        request:xhr,
                        // 响应状态码
                        status:xhr.status,
                        // 响应状态字符串
                        statusText:xhr.statusText
                    })
                }else{
                    reject(new Error('请求失败'));
                }
            }
        }
    })
}

// 4.创建axios函数
let axios = Axios.prototype.request.bind(null);
axios({
    method:'GET',
    url:'http://localhost:3000/posts'
}).then(res=>{
    console.log(res)
})

四、模拟拦截器实现过程

//构造函数
function Axios(config){
    this.config = config;
    this.interceptors = {
        request: new InterceptorManager();
        response: new InterceptorManager()
    }
}

Axios.prototype.request = function(config){
    let promise = Promise.resolve(config);
    // 创建chain数组
    let chains = [dispatchRequest,undefined];
    // 处理拦截器
    // 请求拦截器 将它的回调压入chain的前面
    this.interceptors.request.handlers.forEach(item =>{
       chains.unshift(item.fulfilled,item.rejected)
    })
    // 请求拦截器 将它的回调压入chain的尾部
    this.interceptors.response.handlers.forEach(item =>{
       chains.push(item.fulfilled,item.rejected)
    })
    console.log(chains);

    while(chains.length > 0){
        promise = promise.then(chains.shift(),chains.shift())
    }
        return promise
}
function dispatchRequest(config){
        return new Promise((resolve, reject)=>{
             resolve({
                 state:200,
                 statusText:'success'
             })
        })
}

// 创建实例
let context = new Axios({});
let axios = Axios.prototype.request.bind(context);
// 将context 属性 config 和 interceptors添加至axios函数对象身上
Object.keys(context).forEach(key =>{
    axios[key] = context[key]
})
// 拦截器管理器构造函数
function InterceptorManager(){
   this.handlers = [];
}

// 拦截器管理器原型
InterceptorManager.prototype.use = function(fulfilled, rejected){
   this.handlers.push({
        fulfilled,
        rejected
    })
}

// 设置请求拦截器,config配置对象
axios.interceptors.request.use(function(config){
     console.log('请求拦截器');
     return config;
}, function(error){
     console.log('请求拦截器');
     return Promise.reject(error);
})

// 设置响应拦截器,config配置对象
axios.interceptors.response.use(function(config){
    console.log('响应拦截器');
    return config;
}, function(error){
    console.log('响应拦截器');
    return Promise.reject(error);
});

console.dir(axios);

// 发送请求
axios({
    url: 'http://localhost:3000/posts',
    method: 'post',
    data: {
      username: 'admin',
      password: '123456'
    }
}).then(function(res){
            console.log(res);
}).catch(function(err){
            console.log(err);
})

五、总结

5.1、axiosAxios的关系?

  1. 从语法上说:axios不是Axios的实例
  2. 从功能上说:axiosAxios的实例
  3. axiosAxios.proptype.request函数bind()返回的函数
  4. axios作为对象有Axios原型对象上所有方法,有Axios对象上所有属性

5.2、instanceaxios的区别?

相同点:
  1. 都是一个能发任意请求的函数:request(config)
  2. 都有发特定请求的各种方法:get()/post()/put()/delete()
  3. 都有默认配置和拦截器的属性:defaults/interceptors
不同点:
  1. 默认配置很可能不一样
  2. instance没有axios后面添加的一些方法:create()/CancelToken()

5.3、axios整体流程

image.png

  1. 整体流程
    request(config) ==> dispatchRequest(config) ==> xhrAdapter(config)
  1. request(config)
    将请求拦截器/dispatchRequest()/响应拦截器 通过promise链串连起来,返回promise
  1. dispatchRequest(config)
    转换请求拦数据 ==> 调用xhrAdapter()请求 ==> 请求返回后转换响应数据,返回promise
  1. xhrAdapter(config)
    创建XHR对象,根据config进行相应设置,发送特定请求,并接收响应数据,返回promise

5.4、axios的请求/响应拦截器是什么?

image.png

  1. 请求拦截器
  • 在真正发送请求前执行的回调函数
  • 可以对请求进行检查或配置进行特定处理
  • 成功的回调函数,传递的默认是config(也必须是)
  • 失败的回调函数,传递的默认是error
  1. 响应拦截器
  • 在请求得到响应后执行的回调函数
  • 可以对响应数据进行特定处理
  • 成功的回调函数,传递的默认是response
  • 失败的回调函数,传递的默认是error

5.5、axios的请求/响应数据转换器是什么?

  1. 请求转换器:对请求头和请求体数据进行特定处理的函数
if (lutils.isObject(data)) {
    setContentTypelfUnset(headers, 'application/json;charset=utf-8')
    return JSON.stringify(data);
}
  1. 响应转换器:将响应体json字符串解析为js对象或数组的函数
response.data = JSON.parse(response.data)

5.6、response整体结构

data,
status,
statusText,
headers,
config
request

5.7、Error整体结构

message,
response,
request,

5.8、如何取消未完成的请求?

  1. 当配置了cancelToken对象时,保存cancel函数
  • 创建一个用于将来中断请求的cancelPromise
  • 并定义了一个用于取消请求的cancel函数
  • cancel函数传递出来
  1. 调用cancel()取消请求
  • 执行cacel函数,传入错误信息message
  • 内部会让cancelPromise变为成功的值为一个Cance对象
  • cancelPromise的成功回调中中断请求,并让发请求的proimse失败,失败的reasonCancel对象