axios 的拦截器原理

1,157 阅读3分钟

axios 的拦截器原理

使用拦截器

//添加 请求拦截器
axios.interceptors.request.use((config)=>{
  return config
},(error)=>{
  return Promise.reject(error);
});

//添加 响应拦截器
axios.interceptors.response.use((response)=> { 
  return response;
}, (error)=> {
  return Promise.reject(error);
});

axios.get('https://xxxx').then(res => {
  console.log(res); 
})

可以在发起发起请求前,后响应返回后,进行自定义行为,拦截器可以设置多个

原理

下面涉及到的代码多数来自源码,为方便调试,有部分删除

使用 axios时候

//当使用 axios 或 axios.create 实际 就是调用 createInstance() 

var axios = createInstance(defaults);  
 
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

createInstance 代码

//实例化 Axios 并 返回 Axios.prototype.request
//故Axios.prototype.request是Axios的核心方法,所有的get、post...都是它的语法糖

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

  return instance;
}

Axios代码内容

// 会实例化 InterceptorManager 是拦截器的核心类
function Axios(instanceConfig) {
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}


//Axios 核心 方法 request 中处理拦截器的逻辑如下
//先贴出来,后面介绍其功能
Axios.prototype.request = function request(config) {

  // Hook up interceptors middleware
  var chain = [dispatchRequest, undefined];
  var promise = Promise.resolve(config);
  
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);//在 [dispatchRequest, undefined] 前面 添加
  });

  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);//在 [dispatchRequest, undefined]后面 添加
  });

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

InterceptorManager 代码

'use strict'; 
//核心类 维护一个数据 
function InterceptorManager() {
  this.handlers = [];
}

//use => 向数组中添加拦截器逻辑 并 返回 拦截器索引(后面eject会用到)
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};

//清除拦截器 通过use返回的id
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null;
  }
};

//request 中会有到该方法,是request维护队列的核心方法
InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
};

module.exports = InterceptorManager;

关于拦截器的核心源码如上,先就文章最初的Dome分析,逻辑处理

// 1、当使用 axios 时,Axios被实例化,并内部分别实例化 request、response拦截器

// 2、当使用 use 添加拦截器时,触发 InterceptorManager.prototype.use 方法,将传过来的resove、reject回调,添加在this.handlers[]中

// 3、使用 axios.get('https://xxxx') 时,触发 Axios.prototype.request ,内部触发 InterceptorManager.prototype.forEach

// 3.1 request拦截器 和 response拦截器 触发 Axios.prototype.request 内方法不一样 分别是:this.interceptors.request.forEach、this.interceptors.response.forEach 二者主要区别在于想队列 chain[] 中添加的位置不同,request拦截器从头开始添加 和 response拦截器从尾开始添加,而chain[]本身就含有xhr的 promise 调用返回

// 3.2 从而实现:先触发request拦截器=>发请求,请求返回=>触发response拦截器

// 4 Axios.prototype.request 最后 return promise,就是我们最后使用的axios.get('')这个方法的原样,所有axios.get('').then(res)会再被调用,res就是请求返回的数据被response拦截器处理后的最终结果了

注意下面代码:

// 我们修改最初的Dome,注意传入的回调加上了name:A、B、C、D,方便调试
axios.interceptors.request.use(function A(config) {
    console.log(1);
    return config
});
axios.interceptors.request.use(function B(config) {
    console.log(2);
    return config;
});

axios.interceptors.response.use(function C(res) {
    console.log(3); 
    return res
});
axios.interceptors.response.use(function D(res) {
    console.log(4);
    return res
});
axios.get('https://api.github.com/users/mzabriskie').then(res => {
	console.log(5); 	
})
// 打印结果:2 1 3 4 5
// 因为 Axios.prototype.request 中维护的队列 chain[] 的变化过程如下:

chain[xhr,undefined] => chain[A,undefined,xhr,undefined] => chain[B,undefined,xhr,undefined] => chain[C,undefined,xhr,undefined] => chain[D,undefined,xhr,undefined]

// request拦截器的调用顺序和写入的顺序相反


Axios.js发送请求的核心的逻辑,在 defaults.js中,我们分析一下:

// Axios.js
// 当我们使用 createInstance 传入的 defaults ,就是发送请求的核心
var defaults = require('./defaults');
var axios = createInstance(defaults); 

// defaults.js
// 如下 getDefaultAdapter 是 defaults 核心逻辑
var defaults = {
  adapter: getDefaultAdapter(),
  ...
}

function getDefaultAdapter() {
  var adapter;
  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');
  }
  return adapter;
}

module.exports = defaults;

//./adapters/xhr.js
// 使用原生 XMLHttpRequest 实现 Ajax
module.exports = function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
     

    var request = new XMLHttpRequest(); 
      
    request.open(...);
 
    request.timeout = config.timeout;
 
    request.onreadystatechange = function handleLoad() { 
      ...
    };
 
    request.onabort = function handleAbort() { 
      ...
    };
 
    request.onerror = function handleError() {
      ...
    };
 
    request.ontimeout = function handleTimeout() {
      ...
    };
      
  
    request.send(requestData);
  });
};

思考:最后的Dome中 5 为啥最后打印?

// dispatchRequest 就是答案
var chain = [dispatchRequest, undefined]

//
module.exports = function dispatchRequest(config) {
  // adapter 就是 defaults.js 中处理的返回值
  var adapter = config.adapter || defaults.adapter;

  return adapter(config).then(function onAdapterResolution(response) {
    ...
    console.log(6)
    return response;
  }, function onAdapterRejection(reason) {
  	...
    return Promise.reject(reason);
  });
};
// chain = [dispatchRequest, undefined]中的dispatchRequestadapter(config).then()

// 如果在 dispatchRequestadapter(config).then() 加一个 console.log(6),如上

// 最后打印结果:2 1 6 3 4 5
// Axios.prototype.request 最后返回的 promise 是chain[]被 while 后的的 promise

while (chain.length) {
	promise = promise.then(chain.shift(), chain.shift());
}
//while就是按照队列顺序处理所有的拦截器和xhr逻辑,巧妙的利用 promise.then来处理

// 所以最后打印 5 的then(res) 中的res就是 while 后传给这个then的