⽤axios和后端接⼝进⾏数据交互,那么axios内部实现原理到底是
什么样的
axios源码学习
Axios 是⼀个基于 promise 的 HTTP 库,可以⽤在浏览器和 node.js中进⾏使⽤。github地址
之前就粗略地阅读了axios的⼀些核⼼源码,最近闲来⽆事,就对axios的构建过程及重要特性的实现⼜进⼀步地去阅读,毕竟是吃饭的家伙嘛,还是要做到⼼⾥有数的。axios的常见⽤法⼏相关特性就不在这⾥⼀⼀罗列了,不清楚的同学可以先移步到这⾥。篇幅有限,node环境相关的学习,⼤家感兴趣的可以进⼀步学习。
源码⽬录
我们分析的源码都在/lib这个⼤⽬录下,⽬录如下。
adaapters 适配器(适配浏览器环境和node环境)
cancel 取消请求类(定义请求取消的类)
core 核⼼功能模块 (请求定义及实现的核⼼逻辑)
helper (辅助功能模块)
axiox.js(axios导出的⽂件,⼊⼝⽂件)
default.js (axios的默认配置⽂件)
utils (常⽤的⼯具类⽅法)
axios⼯作流程图
开局⼀张图,剩下全靠编。 (浏览器环境)
axios⼯作原理解析
我们先不关注axios的⼀些扩展⽅法,先理清axios是如何进⾏⼯作的,我们先到⼊⼝⽂件axios.js
varbind =require('./helpers/bind');// 绑定上下⽂函数varAxios =require('./core/Axios');// 核⼼代码⼊⼝varmergeConfig
=require('./core/mergeConfig');// 合并对象vardefaults =require('./defaults');// 默认配置⽂件functioncreateInstance(defaultConfig) {// 创建axios对象实例varcontext =newAxios(defaultConfig);varinstance = bind(quest, context);
我们可以看到,我们⼀直使⽤的axios是通过createInstance这个⽅法返回的
createInstance⾥的返回的axios函数,是对Axios这个核⼼类⽣成的context实例进⾏处理后返回的
有两个关键的⼯具函数extend和bind,对context实例进⾏处理
extend函数functionextend(a, b, thisArg){ forEach(b,functionassignValue(val, key){if(thisArg &&typeofval ==='function') {
a[key] = bind(val, thisArg); }else{// 将b对象上的key value, 赋值到a对象上a[key] = val; } });returna;}bind函数 基本于原⽣js的bind ⽅法没有区别,其实就是返回quest函数functionbind(fn, thisArg){returnfunctionwrap(){varargs
=newArray(arguments.length);for(vari =0; i < args.length; i++) { args[i] =arguments[i]; }returnfn.apply(thisArg, args); };}复制代码
这么⼀顿骚操作后,在createInstance返回的axios函数的属性上,就已经挂载了Axios原型上的⽅法和属性了。
核⼼类Axios
Axios类的实现在core/Axios.js中,
functionAxios(instanceConfig){this.defaults = instanceConfig;this.interceptors = {//
request:newInterceptorManager(),response:newInterceptorManager() };}quest =fu
nctionrequest(config){// Allow for axios('example/url'[, config]) a la fetch APIif(typeofconfig ==='string') { config =arguments[1] || {}; config.url
=arguments[0]; }else{ config = config || {}; }// 合并默认配置和⽤户⾃定义配置config = mergeConfig(this.defaults,
config);hod) { hod = LowerCase(); }elseif(hod) { hod
=LowerCase(); }else{ hod ='get'; }// 定义promise链varchain =
[dispatchRequest,undefined];varpromise =solve(config);// 请求是向前插⼊的,所以函数执⾏的顺序是倒序的quest.forEach(functionunshiftRequestInterceptors(interceptor){ chain.unshift(interceptor.fulfilled, jected); });// 相应是向后插⼊的,所以函数执⾏的顺序是正续的
sponse.forEach(functionpushResponseInterceptors(interceptor){ chain.push(interceptor.fulfilled, jected); });// 当且仅但chain链的为空时while(chain.length)
{ promise = promise.then(chain.shift(), chain.shift()); }//最后返回⼀个promisereturnpromise;};复制代码
我们看下这个Axios实例化跟request函数做了什么事情。
⾸先对默认的config和⾃定义的config进⾏了合并处理。
接着定义了promise链chain数组,并且通过solve(config)⽣成⼀个新的promise,接着对请求和相应插⼊到chain数组中。
判断chain数组长度,如果length不等于0,不断的更新promise的值,最后返回最终的promise。
这也是为什么我们可以链式调⽤的axios的原因,因为最终返回的是⼀个promise对象。接着我们来⼀⼀对其中的重要的组成部分进⾏分析。
dispatchRequest
代码在core/dispatchRequest.js下,这⾥我们先不关注请求取消的逻辑,只看请求发送的逻辑部分
(config);// Ensure headers existconfig.headers = config.headers || {};// Transform request dataconfig.data = transformData( config.data,
config.headers, ansformRequest );// Flatten headersconfig.headers = ( config.headersmon || {}, config.hod] || {}, config.headers ); utils.forEach(
['delete','get','head','post','put','patch','common'],functioncleanHeaderConfig(method){deleteconfig.headers[method]; }
);varadapter = config.adapter || defaults.adapter;returnadapter(config).then(functiononAdapterResolution(response){ throwIfCancellationRequested(config);// Transform response dataresponse.data = transformData( response.data, response.headers, ansformResponse );returnresponse; },functiononAdapterRejection(reason)
js合并两个数组{if(!isCancel(reason)) { throwIfCancellationRequested(config);// Transform response dataif(reason && sponse) { sponse.data = transformData( sponse.data, sponse.headers,
dispatchRequest函数的逻辑⾮常清晰明了
⾸先会对通过transformData对config的data、headers、transformRequest属性进⾏处理,这⾥也验证了流程图汇总,在发送xhr前,会对confgi进⾏transformData的处理
transform函数varutils =require('./../utils');ports =functiontransformData(data, headers, fns){/*eslint no-param-reassign:0*/utils.forEach(fns,functiontransform(fn){ data = fn(data, headers); });returndata;};复制代码
可以从代码中看出传⼊的fns其实是⼀个函数数组,循环遍历函数数组,将当前的data和headers属性当作参数传⼊每⼀个fn中,并将返回值作为新的data的值,从⽽达到对config.data的多次处理,从官⽅⽂档中也可以看到这样的⽤法
// `transformRequest` 允许在向服务器发送前,修改请求数据// 只能⽤在 'PUT', 'POST' 和 'PATCH' 这⼏个请求⽅法// 后⾯数组中的函数必须返回⼀个字符串,或 ArrayBuffer,或 StreamtransformRequest: [function(data, headers){// 对 data 进⾏任意转换处理returndata; }],// `transformResponse` 在传递给 then/catch 前,允许修改响应数据transformResponse: [function(data){// 对 data 进⾏任意转换处理returndata; }],复制代码
对config.headers字段进⾏了深度合并,接着将headers上绑定的请求⽅法名遍历循环删除掉
通过config.adpater来确定是node环境还是浏览器环境,从⽽决定是调⽤node.js的http模块⽅法还是浏览的XHR⽅法。在默认的配置⽂件中core/default.js
functiongetDefaultAdapter(){varadapter;if(typeofXMLHttpRequest !=='undefined') {// For browsers use XHR adapteradapter
=require('./adapters/xhr'); }elseif(typeofprocess !=='undefined'&&String.call(process) ==='[object process]') {// For node use HTTP adapteradapter =require('./adapters/http'); }returnadapter;}复制代码
通过getDefaultadapter⽅法,去引⽤不同的⽂件,浏览器环境的话就直接⾛到
adapter =require('./adapters/xhr');复制代码
最后会对请求的返回的响应结果进⾏transofromData的处理,与上⾯处理请求参数的原理相似。
xhr
...
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论