axios源码分析--请求流程

axios 是一个基于 promise 的 HTTP 库,能够用在浏览器和 node.js 中。本质上也是对原生XHR的封装。node

一、使用方法

axios.defaults.baseURL = 'http://xxx.com/api';
axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

axios.interceptors.request.use(resolveFn1, rejectFn2); // 添加请求拦截器
axios.interceptors.response.use(resolveFn2, rejectFn2); // 添加响应拦截器

axios('/user?ID=12345').then(() => {
    // 请求成功的处理
  }, () => {
    // 请求异常的处理
  }
);

axios.get('/user?ID=12345').then(function (response) {
    //
}).catch(function (error) {
    //
}).finally(function () {
    //
});

复制代码

源码分析

加载axios文件后,能够直接使用axios.get,能够把axios看做是一个对象,找到axios/lib/axios.js文件,就能够直接看到var axiosios

var axios = createInstance(defaults);
复制代码

defaults是一个对象,一些默认配置值ajax

{
    adapter: ƒ xhrAdapter(config)
    headers: {
        common: { Accept: "application/json, text/plain, */*" }, 
        delete: {}, 
        get: {}, 
        head: {}, 
        patch:{ Content-Type: "application/x-www-form-urlencoded" }
        post: { Content-Type: "application/x-www-form-urlencoded" }
        put: { Content-Type: "application/x-www-form-urlencoded" }
    }
    maxContentLength: -1
    timeout: 0
    transformRequest: [ƒ]
    transformResponse: [ƒ]
    validateStatus: ƒ validateStatus(status)
    xsrfCookieName: "XSRF-TOKEN"
    xsrfHeaderName: "X-XSRF-TOKEN"
}
复制代码

createInstance方法具体实现json

function createInstance(defaultConfig) {
  //new Axios的实例,context就是一个对象了
  var context = new Axios(defaultConfig);
  //instance是一个函数,是Axios.prototype.request方法
  var instance = bind(Axios.prototype.request, context);

  // 复制Axios.prototype到instance
  utils.extend(instance, Axios.prototype, context);

  //复制context到instance
  utils.extend(instance, context);

  return instance;
}
复制代码

先看看Axios构造函数是什么,建立了一个怎么样的实例。在axios/lib/core/Axios.jsaxios

function Axios(instanceConfig) {
  //两个属性
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}
//原型对象上方法
Axios.prototype.request = function(){...}
Axios.prototype.getUri = function(){...}

utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});
复制代码

构造函数Axios.prototype上,终于看到有get方法的定义,get方法最终仍是调的request方法。context是Axios的实例,可是axios却不是Axios的实例化对象api

那instance又是什么呢?如何将axios和context对应上呢?promise

接着往下看bind浏览器

function bind(fn, thisArg) {
  return function wrap() {
    var args = new Array(arguments.length);
    for (var i = 0; i < args.length; i++) {
      args[i] = arguments[i];
    }
    return fn.apply(thisArg, args);
  };
};
复制代码

bind返回一个函数wrap,暂且不看里面得话,咱们就能够知道instance是一个函数,那么当instance执行的时候,其实就是执行Axios.proptotype.request,this指向context,说白了bind就是改变了this指向,createInstance函数返回就是这个instance,因此axios就是instance,就是一个函数bash

function createInstance(defaultConfig) {
  //...
  // 复制Axios.prototype到instance
  utils.extend(instance, Axios.prototype, context);
  //复制context到instance
  utils.extend(instance, context);

  return instance;
}
复制代码

咱们探探extend究竟作了什么,找到axios/lib/utils.js服务器

// extend就是把b的属性都复制个a

function extend(a, b, thisArg) {
  forEach(b, function assignValue(val, key) {
//若是属性的值是一个方法的话,就改变this的指向到thisArg再复制给a 
    if (thisArg && typeof val === 'function') {
      a[key] = bind(val, thisArg);
    } else {
      a[key] = val;
    }
  });
  return a;
}
复制代码

instance继承了Axios.prototype和context的全部的属性,属性值是方法的话,里面的this都是指向context的。instance就是axios。

回到最开始axios.get的调用,其实就是调用Axios.prototype.get。Axios.prototype.get内部最终调用Axios.prototype.request

Axios.prototype.request = function request(config) {
  //...
  //开始就对config进行判断,合并默认配置
  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;
};
复制代码

Axios.prototype.request返回promise,因此能够链式调用axios.get(''/user?ID=12345'').then().catch()。

while循环执行dispatchRequest,继续看看它究竟作了什么? 找到axios/lib/core/dispatchRequest.js文件

function dispatchRequest(config){
    // ...
    // 开始就对config的 baseUrl, data, headers进行处理  
    var adapter = config.adapter || defaults.adapter;
    // 执行adapter是一个Promise对象,resolve的函数的参数仍是response
    // adpater确定是去发送请求了啊
    return adapter(config).then(function(response){
        // ...
        return response
    }, function(reason){
        // ...
        return Promise.reject(reason)
    })
}
复制代码

调用了adapter,config中没有,看defaults.adapter。进入到axios/lib/defaults.js文件

var defaults = {
  adapter: getDefaultAdapter(),
  //...
}

function getDefaultAdapter() {
  var adapter;
  if (typeof XMLHttpRequest !== 'undefined') { //浏览器环境
    adapter = require('./adapters/xhr');
  } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
    //node环境
    adapter = require('./adapters/http');
  }
  return adapter;
}
复制代码

adapter是区分浏览器和node环境的,那么咱们就去axios/lib/adapter/xhr.js看下浏览器环境的,就是封装了一个promise进行ajax请求服务器。

至此回过头来再看开始那个get请求,axios.get继承于Axios.prototype.get,其实就是去执行Axios.prototype.request,返回一个promsie对象,因此可使用then和catch接收返回值和处理错误。进行ajax请求的主要函数就是adapter,adapter区分了一下浏览器和node环境

思考

为何不将全部方法在Axios上实现而后返回new Axios呢?

根据源码可知,axios实例(instance)是对Axios.prototype.request方法包裹了一层函数,主要是为将Axios.prototype.request内部的this绑定到新建的Axios对象(context)上。而后经过 utils.extend 将内部context和Axios.prototyp的属性添加到这个Axios.prototype.request方法(intance)上,添加上去的函数也会绑定this到新建的Axios对象(context)上。最终的axios实例上面的方法内部的this指向的都是新建的Axios对象(context),从而使得不一样axios实例之间隔离了做用域,能够对每一个axios实例设置不一样的config。

axios('/user?ID=12345').then(() => {
    // 请求成功的处理
  }, () => {
    // 请求异常的处理
  }
);
复制代码

由于axios内部调用的都是Axios.prototype.request方法,Axios.prototype.request默认请求方法为get。为了让开发者能够直接axios(config)就能够发送get请求,而不须要axios.get(config)。若是直接new一个Axios对象是没法实现这种简写的(没错,就是为了少打几个字)

function request(config) {
  if (typeof config === 'string') {
    config = arguments[1] || {};
    config.url = arguments[0];
  } else {
    config = config || {};
  }

  //合并参数
  config = mergeConfig(this.defaults, config);

  if (config.method) {
    config.method = config.method.toLowerCase();
  } else if (this.defaults.method) {
    config.method = this.defaults.method.toLowerCase();
  } else {
    config.method = 'get'; //默认调用get方法
  }
  //...
复制代码

实际上axios.post、axios.put等全部axios的请求方法内部都是调用Axios.prototype.request

参考连接

axios执行原理了解一下!

axios源码分析——请求流程