Axios源码解析

889 阅读3分钟

Axios整体流程

image.png

  • request(config):将请求拦截器/dispatchRequest()/响应拦截器通过promise链串联起来,返回promise
  • dispatch(config):转换请求数据 -> 调用xhrAdapter()发起请求->请求返回后转换响应数据,返回promise
  • xhrAdapter(config):创建XHR对象,根据config进行相应配置,发送特定请求,并接收响应数据,返回promise

支持浏览器和NODE两种环境

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; 
}
  • xhrAdapter会通过new XMLHttpRequest()生成xhr对象,然后发送请求
  • httpAdapter会调用node端内置的http模块发起请求

get/post/put/ 的实质

function createInstance(defaultConfig) { 
  var context = new Axios(defaultConfig); 
  var instance = bind(Axios.prototype.request, context); 
  // Copy axios.prototype to instance 
  utils.extend(instance, Axios.prototype, context); 
  // Copy context to instance 
  utils.extend(instance, context); 
  return instance; 
 }
  • context是Axios实例,上面有defaults( 默认配置)和interceptors(拦截器)两个属性。
  • 通过bind函数返回一个新函数,并赋值给instance。所以执行instanc(),其实就是在执行Axios.prototype.request。也就是发送请求的函数。
  • 把Axios.prototype的方法都拷贝到instance上
  • 把context实例对象上的属性拷贝到instance上
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 })); 
 }; 
 });

将get/post/......等方法通过循环的方式一一挂载到Axios的原型上,而instance已经拷贝了Axios原型上的所有方法。所以可以通过instance.get(config)去发送请求。所有方法的本质最终都是去调用request函数,传入相应的method。

拦截器

  • 声明一个chain数组[dispatchRequest,undefined]
  • 请求拦截器的回调放到chain前面
  • 响应拦截器的回调放到chain后面

image.png

拦截器的生成:

function Axios(instanceConfig) { 
this.defaults = instanceConfig; 
this.interceptors = { 
  request: new InterceptorManager(), 
  response: new InterceptorManager() 
 }; 
}
InterceptorManager.prototype.use = function use(fulfilled, rejected, options) { 
  this.handlers.push({ 
    fulfilled: fulfilled, 
    rejected: rejected, 
    synchronous: options ? options.synchronous : false, 
    runWhen: options ? options.runWhen : null }); 
    return this.handlers.length - 1; };

use在执行的时候只是把传入的回调保存在了this.handlers中,在request函数中把请求拦截器的回调往数组chain前放,把响应拦截器的回调往数组chain后面放,然后通过promise的then()串连起所有的请求拦截器/请求方法/响应拦截器

// 请求拦截器
var requestInterceptorChain = []; 
this.interceptors.request.handlers.forEach(function unshiftRequestInterceptors(interceptor) {  
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected); });
// 响应拦截器 
var responseInterceptorChain = [];
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected); });
var promise; 
var chain = [dispatchRequest, undefined]; 
Array.prototype.unshift.apply(chain, requestInterceptorChain); 
chain = chain.concat(responseInterceptorChain); promise = Promise.resolve(config); 
// 依次执行chain中的所有方法 while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); } 
return promise;

取消请求--cancelToken

  • 配置cancelToken
  • 缓存用于取消请求的函数
  • 在特定时间调用cancel函数取消请求
  • 本质是调用xhr.abort()方法中断请求
if (config.cancelToken) { 
// Handle cancellation config.cancelToken.promise.then(function onCanceled(cancel) { 
  if (!request) { return; } 
  request.abort(); 
  reject(cancel); 
  // Clean up 
  request request = null; 
  }); 
 }

代码中配置:

let cancel // 用于保存取消请求的函数 
function getData() { 
  axios({ 
   url: '***', 
   cancelToken: new axios.CancelToken((c) => { 
   // 保存取消函数,用于之后可能需要取消当前请求
   cancel = c }) 
  }).then(res => { const data = res.data }) }

当执行cancel函数时就会执行下面的resolvePromise,改变this.promise为成功的状态,然后xhrAdapter中就会执行config.cancelToken.promise.then执行abort

function CancelToken(executor) { 
  if (typeof executor !== 'function') { 
    throw new TypeError('executor must be a function.'); 
   } 
   var resolvePromise; 
   this.promise = new Promise(function promiseExecutor(resolve) {
     resolvePromise = resolve; 
   });
   var token = this; 
   executor(function cancel(message) { 
   if (token.reason) { 
   // Cancellation has already been requested return; 
   } 
   token.reason = new Cancel(message); 
   resolvePromise(token.reason); 
  });
 }