axios 源码解析

axios请求响应流程:
在这里插入图片描述
定义axios类

function Axios(instanceConfig) {
  //接受默认的配置参数
  this.defaults = instanceConfig;
  //定义请求和响应的拦截器
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

request

  • 实际发送请求的方法

  • 对用户传进来的配置参数进行合并

  • 将用户配置的请求拦截(unshift)和响应拦截(push)添加到执行队列中

  • 执行请求拦截函数---->发起请求----->执行响应拦截函数

Axios.prototype.request = function request(config) {
  //这里就是对参数进行预处理,因为axios传入参数的方式有好几种
  if (typeof config === 'string') {
    config = arguments[1] || {};
    config.url = arguments[0];
  } else {
    config = config || {};
  }
  //将我们配置的参数和原始参数进行合并
  config = mergeConfig(this.defaults, config);

  // 设置请求方法
  if (thod) {
    config.methconfig.meod = config.method.toLowerCase();
  } else if (this.defaults.method) {
    config.method = this.defaults.method.toLowerCase();
  } else {
    config.method = 'get';
  }

  //请求流程的调用链,处理函数都是成对出现,对应Promise的resolve和rejejct处理函数,
  //当没有添加拦截器时,默认传入的是dispatchRequest和undefine
  //dispatchRequest内部会发起真正XML的请求。
  var chain = [dispatchRequest, undefined];
  //将传入的配置进行预先设置,方便拦截器中获取
  var promise = Promise.resolve(config);
  //将用户添加的请求拦截函数放入chain中,采用的是unshift插入,在后面定义的请求拦截器反而最先执行
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });
 //将用户添加的响应拦截函数放入chain中,采用的是push方式
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });
  //循环执行弹出调用链中的方法,每个调用链中函数的返回值作为下一个函数的参数,最后promise拿到的就是最终的数据
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

添加其他原型方法

  • 本质上还是调用request
//将['delete', 'get', 'head', 'options']方法加入到Axios的原型中
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
    }));
  };
});
//将['post', 'put', 'patch']方法加入到Axios的原型中
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  Axios.prototype[method] = function(url, data, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});

axios实例

function createInstance(defaultConfig) {
  //创建axios对象,将默认配置参数传入
  var context = new Axios(defaultConfig);
  //通过bind方法重新创建一个函数,并将Axios原型上的方法和属性复制给这个函数
  var instance = bind(Axios.prototype.request, context);
  utils.extend(instance, Axios.prototype, context);
  utils.extend(instance, context);
  return instance;
}
//创建请求方法,这个对象就是我们最终发送请求使用的方法,这一提醒下axios是一个函数。
var axios = createInstance(defaults);

bind

  • instance实际上就是wrap方法,通过上面的utils.extend,将Axios原型上的方法复制给了wrap函数
  • args就是我们发送请求时配置的参数,最终args会交给request函数
function bind(fn, thisArg){
	return function wrap(...args){
		return fn.apply(thisArg,args);
	}
}

调用dispatchRequest发起请求
transformData

  • 首先调用transformData转换参数
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );
  • 如果我们想自定义转换函数可以通过concat添加自定义的函数
axios.get('xxxx', {
  transformResponse: axios.defaults.transformResponse.concat(function (data, headers) {
    //....
    return data;
  })
})

adapter

  • 通过adapter适配器进行不同平台请求的适配,比如浏览器为xhr,node为http
  • 通过是否存在XMLHttpRequest、process进行不同平台的判断
var adapter = config.adapter || defaults.adapter;

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

function getDefaultAdapter() {
  var adapter;
  //根据XMLHttpRequest判断当前是不是浏览器环境,如果不是就根据process判断是不是在node环境
  if (typeof XMLHttpRequest !== 'undefined') {
    adapter = require('./adapters/xhr');
  } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
    adapter = require('./adapters/http');
  }
  return adapter;
}

浏览器环境:

  • 通过xhr发起请求
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() {
      //...处理返回的数据
      settle(resolve, reject, response);
      request = null;
    };
    //监听中断操作
    request.onabort = function handleAbort() {};
    //监听错误
    request.onerror = function handleError() {};
    //监听超时
    request.ontimeout = function handleTimeout() {};
    if (requestData === undefined) {
      requestData = null;
    }
    request.send(requestData);
  });
};

adapter调用

return adapter(config).then(function onAdapterResolution(response) {
    throwIfCancellationRequested(config);
    //....对response进行处理
    return response;
  }, function onAdapterRejection(reason) {
    if (!isCancel(reason)) {
      throwIfCancellationRequested(config);

      // Transform response data
      if (reason && reason.response) {
        //...对reason进行处理
      }
    }
    return Promise.reject(reason);
  });

监听上传下载

  • 浏览器端实际上是通过原生xhr注册事件实现
//监听下载进度    
if (typeof config.onDownloadProgress === 'function') {
      request.addEventListener('progress', config.onDownloadProgress);
}
//监听上传进度
if (typeof config.onUploadProgress === 'function' && request.upload) {
      request.upload.addEventListener('progress', config.onUploadProgress);
}

中断请求
基本使用:

const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('url', {
  cancelToken: source.token
}).then((res) => {
    console.log(res)
}).catch((err) => {
  console.log(err);
});
source.cancel('中断请求原因');

CancelToken

  • executor中传入的cancel实际就是用户调用的source.cancel方法
function CancelToken(executor) {
  var resolvePromise;
  this.promise = new Promise(function promiseExecutor(resolve) {
    //将promise的执行器暴露出去
    resolvePromise = resolve;
  });
  var token = this;
  executor(function cancel(message) {
    if (token.reason) {
      return;
    }
    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}

CancelToken.source

  • 在该函数中进行CancelToken的实例化,并向外暴露用户需要调用的内容,只有用户主动调用才会执行
  • token上的promise用来控制请求的中断
CancelToken.source = function source() {
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  return {
    token: token,
    cancel: cancel
  };
};

执行请求中断

  • 当用户执行了source方法后,会执行cancelToken的resolve,继而调用下面的回调
  • 在xhrAdapter中,当发起请求过程中,如果用户调用了source,则会通过xhr.abort中断请求,并执行reject(作为axios返回的Promise的结果)和清除request
  • 如果用户没有调用source,则下面的promise会一直处于pending状态,不影响xhr的正常发送
if (config.cancelToken) {
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
          return;
        }
		//通过xhr.abort中断请求
        request.abort();
        reject(cancel);
        request = null;
      });
}

CSRF防御

  • 双重cookie防御
  • 利用csrf只能携带cookie,不能操作cookie的原因,再次请求时从Cookie中取出这个字符串,添加到URL参数中,然后服务器通过对Cookie中的数据和参数中的数据对比验证
// lib/defaults.js
var defaults = {
  adapter: getDefaultAdapter(),

  // 省略部分代码
  xsrfCookieName: 'XSRF-TOKEN',
  xsrfHeaderName: 'X-XSRF-TOKEN',
};


// lib/adapters/xhr.js
module.exports = function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    var requestHeaders = config.headers;
    
    var request = new XMLHttpRequest();
    // 省略部分代码
    
    // 添加xsrf头部
    //浏览器环境
    if (utils.isStandardBrowserEnv()) {
      //允许跨域携带cookie并且同源
      var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ?
        cookies.read(config.xsrfCookieName) :
        undefined;

      if (xsrfValue) {
        requestHeaders[config.xsrfHeaderName] = xsrfValue;
      }
    }

    request.send(requestData);
  });
};

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值