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源碼分析——請求流程

相關文章
相關標籤/搜索