axios现在最新的版本的是v0.19.0,本节我们来分析一下它的实现源码,首先通过 gitHub地址获取到它的源代码,地址:https://github.com/axios/axios/tree/v0.19.0

下载后就可以看到axios的目录结构,主目录下有一个index.js文件,该文件比较简单,内容如下:

就是去引入./lib/axios模块而已,lib目录内容如下:

大致文件说明如下:

index.js            ;入口文件
    ├lib                ;代码主目录
        ├helpers            ;定义了一些辅助函数
        ├adapters          ;原生ajax和node环境下请求的封装
        ├cancel             ;取消请求的一些封装
        ├core                ;请求派发、拦截器管理、数据转换等处理
        axios.js              ;也算是入口文件吧
        default.js           ;默认配置文件
        utils.js                ;工具函数

writer by:大沙漠 QQ:22969969

./lib/axios应该也可以说是一个入口文件,主要的分支如下:

var utils = require('./utils');
var bind = require('./helpers/bind');
var Axios = require('./core/Axios');
var mergeConfig = require('./core/mergeConfig');
var defaults = require('./defaults'); //默认配置对象 /*略*/ function createInstance(defaultConfig) { //创建一个Axios的实例 参数为:Axios的默认配置
var context = new Axios(defaultConfig);               //创建一个./lib/core/Axios对象,作为上下文
var instance = bind(Axios.prototype.request, context);       //创建一个instance属性,值为bind()函数的返回值 // Copy axios.prototype to instance
utils.extend(instance, Axios.prototype, context);          //将Axios.prototype上的方法(delete、get、head、options、post、put、patch、request)extend到instans上,通过bind进行绑定 // Copy context to instance
utils.extend(instance, context);                   //将context上的两个defaults和interceptors属性保存到utils上面,这两个都是对象,这样我们就可以通过axios.defaults修改配置信息,通过axios.interceptors去设置拦截器了 return instance;                             //返回instance方法
} // Create the default instance to be exported
var axios = createInstance(defaults); //创建一个默认的实例作为输出 /*略*/
module.exports = axios; //导出符号 // Allow use of default import syntax in TypeScript
module.exports.default = axios; //默认导出符号

createInstance会创建一个./lib/core/Axios的一个对象实例,保存到局部变量context中,然后调用bind函数,将返回值保存到instance中(这就是我们调用axios()执行ajax请求时所调用的符号),bind()是一个辅助函数,如下:

module.exports = function bind(fn, thisArg) {        //以thisArg为上下文,执行fn函数
return function wrap() {
var args = new Array(arguments.length); //将arguments按照顺序依次保存到args里面
for (var i = 0; i < args.length; i++) {
args[i] = arguments[i];
}
return fn.apply(thisArg, args); //执行fn函数,参数为thisArg为上下文,args为参数
};
};

该函数是一个高阶函数的实现,它会以参数2作为上下文,执行参数1,也就是以context为上下文,执行Axios.prototype.request函数,Axios.prototype.request就是所有异步请求的入口了

我们看一下Axios.prototype.request的实现,如下:

Axios.prototype.request = function request(config) {            //派发一个请求,也是ajax请求的入口
/*eslint no-param-reassign:0*/
// Allow for axios('example/url'[, config]) a la fetch API
if (typeof config === 'string') { //如果config对象是个字符串, ;例如:axios('/api/1.php').then(function(){},function(){})
config = arguments[1] || {}; //则将其转换为对象
config.url = arguments[0];
} else {
config = config || {};
} config = mergeConfig(this.defaults, config); //合并默认值
config.method = config.method ? config.method.toLowerCase() : 'get'; //ajax方法,例如:get,这里是转换为小写 // Hook up interceptors middleware
var chain = [dispatchRequest, undefined]; //这个是发送ajax的异步对列
var promise = Promise.resolve(config); //将config转换为Promise对象 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) { //如果chain.length存在
promise = promise.then(chain.shift(), chain.shift()); //则执行promise.then(),这里执行dispatchRequest函数,这样就组成了异步队列
} return promise; //最后返回promise对象
};

这里有一个while(chain.length){}遍历循环比较难以理解,这个设计思想很新颖,这里理解了整个axios的执行流程就能理解了,拦截器也是在这里实现的。它就是遍历chain数组,依次把前两个元素分别作为promise().then的参数1和参数2来执行,这样当promise之前的队列执行完后就会接着执行后面的队列,默认就是[dispatchRequest,undefined],也就是首先会执行dispatchRequest,如果有添加了请求拦截器则会在dispatchRequest之前执行拦截器里的逻辑,同样的,如果有响应拦截器,则会在执行dispatchRequest之后执行响应拦截器里的逻辑。

dispatchRequest逻辑如下:

module.exports = function dispatchRequest(config) {                //派发一个到服务器的请求,用config里的配置
throwIfCancellationRequested(config); // Support baseURL config
if (config.baseURL && !isAbsoluteURL(config.url)) { //如果config.baseURL存在,且config.url不是绝对URL(以http://开头的)
config.url = combineURLs(config.baseURL, config.url); //则调用combineURLs将config.baseURL拼凑在config.url的前面,我们在项目里设置的baseURL="api/"就是在这里处理的
} // Ensure headers exist
config.headers = config.headers || {}; //确保headers存在 // Transform request data
config.data = transformData( //修改请求数据,会调用默认配置里的transformRequest进行处理
config.data,
config.headers,
config.transformRequest
); // Flatten headers
config.headers = utils.merge( //将请求头合并为一个数组
config.headers.common || {},
config.headers[config.method] || {},
config.headers || {}
); utils.forEach( //再删除config.headers里的delete、get、head、post、put、patch、common请求头
['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
function cleanHeaderConfig(method) {
delete config.headers[method];
}
);
//执行到这里请求头已经设置好了
var adapter = config.adapter || defaults.adapter; //获取默认配置里的adapter,也就是封装好的ajax请求器 return adapter(config).then(function onAdapterResolution(response) { //执行adapter()就会发送ajax请求了,then()的第一个参数会修正返回的值
throwIfCancellationRequested(config); // Transform response data
response.data = transformData( //调用默认配置里的transformResponse对返回的数据进行处理
response.data,
response.headers,
config.transformResponse
); return response;
}, function onAdapterRejection(reason) {
if (!isCancel(reason)) {
throwIfCancellationRequested(config); // Transform response data
if (reason && reason.response) {
reason.response.data = transformData(
reason.response.data,
reason.response.headers,
config.transformResponse
);
}
} return Promise.reject(reason);
});
};

最后会执行默认配置里的adapter属性对应的函数,我们来看一下,如下:

function getDefaultAdapter() {                //获取默认的适配器,就是Ajax的发送器吧
var adapter;
// Only Node.JS has a process variable that is of [[Class]] process
if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') { //对于浏览器来说,用XHR adapter
// For node use HTTP adapter
adapter = require('./adapters/http');
} else if (typeof XMLHttpRequest !== 'undefined') { //对于node环境来说,则使用HTTP adapter
// For browsers use XHR adapter
adapter = require('./adapters/xhr');
}
return adapter;
} var defaults = {
adapter: getDefaultAdapter(), //适配器
/*略*/
}

./adapters/http就是最终发送ajax请求的实现,主要的逻辑如下:

module.exports = function xhrAdapter(config) {                            //发送XMLHTtpRequest()请求等
return new Promise(function dispatchXhrRequest(resolve, reject) {
var requestData = config.data;
var requestHeaders = config.headers; if (utils.isFormData(requestData)) {
delete requestHeaders['Content-Type']; // Let the browser set it
} var request = new XMLHttpRequest(); // HTTP basic authentication
if (config.auth) {
var username = config.auth.username || '';
var password = config.auth.password || '';
requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
} request.open(config.method.toUpperCase(), buildURL(config.url, config.params, config.paramsSerializer), true); //初始化HTTP请求,采用异步请求 调用buildURL获取URL地址 // Set the request timeout in MS
request.timeout = config.timeout; //设置超时时间 // Listen for ready state
request.onreadystatechange = function handleLoad() { //绑定onreadystatechange事件
if (!request || request.readyState !== 4) { //如果HTTP响应已经还没有接收完成
return; //则直接返回,不做处理
} // The request errored out and we didn't get a response, this will be
// handled by onerror instead
// With one exception: request that using file: protocol, most browsers
// will return status as 0 even though it's a successful request
if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) { //请求出错,没有得到响应的逻辑 如果request.responseURL不是以file:开头且request.status=0,则直接返回
return;
} // Prepare the response
var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null; //解析响应头,并调用parseHeaders将其转换为对象,保存到responseHeaders里面
var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response; //如果未设置config.responseType或者设置了responseType.responseType且等于text,则直接获取request.responseText,否则获取request.response
var response = { //拼凑返回的数据,也就是上一篇说的axios请求后返回的promise对象
data: responseData, //接收到的数据
status: request.status, //状态 ie浏览器是用1223端口代替204端口 ,见:https://github.com/axios/axios/issues/201
statusText: request.statusText, //响应头的状态文字
headers: responseHeaders, //头部信息
config: config, //配置信息
request: request //对应的XmlHttpRequest对象
}; settle(resolve, reject, response); //调用settle函数进行判断,是resolve或者reject // Clean up request
request = null;
}; /*略,主要是对于错误、超时、的一些处理*/ // Add headers to the request
if ('setRequestHeader' in request) { //如果request里面存在setRequestHeader
utils.forEach(requestHeaders, function setRequestHeader(val, key) { //遍历requestHeaders
if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') { //如果key等于content-type 且没有发送数据
// Remove Content-Type if data is undefined
delete requestHeaders[key]; //则删除content-type这个请求头 ;只有发送数据时content-type才有用的吧
} else {
// Otherwise add header to the request
request.setRequestHeader(key, val); //否则设置请求头
}
});
} // Add withCredentials to request if needed
if (config.withCredentials) { //如果设置了跨域请求时使用凭证
request.withCredentials = true; //设置request.withCredentials为true
} // Add responseType to request if needed
if (config.responseType) { //如果设置了服务器响应的数据类型,默认为json
try {
request.responseType = config.responseType;
} catch (e) {
// Expected DOMException thrown by browsers not compatible XMLHttpRequest Level 2.
// But, this can be suppressed for 'json' type as it can be parsed by default 'transformResponse' function.
if (config.responseType !== 'json') {
throw e;
}
}
} // Handle progress if needed
if (typeof config.onDownloadProgress === 'function') { //如果设置了下载处理进度事件
request.addEventListener('progress', config.onDownloadProgress);
} // Not all browsers support upload events
if (typeof config.onUploadProgress === 'function' && request.upload) { //如果设置了上传处理进度事件
request.upload.addEventListener('progress', config.onUploadProgress);
} if (config.cancelToken) {
// Handle cancellation
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
} request.abort();
reject(cancel);
// Clean up request
request = null;
});
} if (requestData === undefined) { //修正requestData,如果为undefined,则修正为null
requestData = null;
} // Send the request
request.send(requestData); //发送数据
});
};

也就是原生的ajax请求了,主要的逻辑都备注了一下,这样整个流程就跑完了

对于便捷方法来说,例如axios.get()、axios.post()来说,就是对Axios.prototype.request的一次封装,实现代码如下:

utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {    //定义delete、get、head、options方法
/*eslint func-names:0*/
Axios.prototype[method] = function(url, config) {
return this.request(utils.merge(config || {}, { //调用utils.merge将参数合并为一个对象,然后调用request()方法
method: method,
url: url
}));
};
}); utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { //定义post、put、patch方法
/*eslint func-names:0*/
Axios.prototype[method] = function(url, data, config) { //调用utils.merge将参数合并为一个对象,然后调用request()方法
return this.request(utils.merge(config || {}, {
method: method,
url: url,
data: data //post、put和patch比get等请求多了个data,其它一样的
}));
};
});

OK,搞定。

axios 源码解析(中) 代码结构的更多相关文章

  1. storm源码之storm代码结构【译】【转】

    [原]storm源码之storm代码结构[译]  说明:本文翻译自Storm在GitHub上的官方Wiki中提供的Storm代码结构描述一节Structure of the codebase,希望对正 ...

  2. storm源码之storm代码结构【译】

    storm源码之storm代码结构[译] 说明:本文翻译自Storm在GitHub上的官方Wiki中提供的Storm代码结构描述一节Structure of the codebase,希望对正在基于S ...

  3. IdentityServer4源码解析_1_项目结构

    目录 IdentityServer4源码解析_1_项目结构 IdentityServer4源码解析_2_元数据接口 IdentityServer4源码解析_3_认证接口 IdentityServer4 ...

  4. jquery源码解析:代码结构分析

    本系列是针对jquery2.0.3版本进行的讲解.此版本不支持IE8及以下版本. (function(){ (21, 94)     定义了一些变量和函数,   jQuery = function() ...

  5. tinyxml源码解析(中)

    转载于:http://www.cnblogs.com/marchtea/archive/2012/11/20/2766756.html 前言: 之前趁着这段时间比较空闲,也因为听闻tinyxml大名, ...

  6. axios源码解析 - 请求拦截器

    axios请求拦截器,也就是在请求发送之前执行自定义的函数. axios源码版本 - ^0.27.2 (源码是精简版) 平时在业务中会这样去写请求拦截器,代码如下: // 创建一个新的实例 var s ...

  7. 【原】storm源码之storm代码结构【译】

    说明:本文翻译自Storm在GitHub上的官方Wiki中提供的Storm代码结构描述一节Structure of the codebase,希望对正在基于Storm进行源码级学习和研究的朋友有所帮助 ...

  8. Laravel学习笔记之Session源码解析(中)

    说明:在上篇中学习了session的启动过程,主要分为两步,一是session的实例化,即\Illuminate\Session\Store的实例化:二是从session存储介质redis中读取id ...

  9. yolo源码解析(1):代码逻辑

    一. 整体代码逻辑 yolo中源码分为三个部分,\example,\include,以及\src文件夹下都有源代码存在. 结构如下所示 ├── examples │ ├── darknet.c(主程序 ...

随机推荐

  1. python免密远程执行shell

    使用paramiko库:https://github.com/paramiko/paramiko 简单封装SSH类 import paramiko class SSH: def __init__(se ...

  2. C#上手练习6(方法语句1)

    方法是将完成同一功能的内容放到一起,方便书写和调用的一种方式,也体现了面向对象语言中封装的特性. 定义方法的语法形式如下. 访问修饰符    修饰符    返回值类型    方法名(参数列表){    ...

  3. 【Gradle】Gradle入门

    Gradle入门 配置Gradle环境 安装之前确保已经安装配置好Java环境,要求JDK6以上,并且在环境变量里配置了JAVA_HOME,查看Java版本可以在终端输入如下命令: java -ver ...

  4. 记录一下自己在MVC项目中如何防CSRF攻击,直接上代码

    1.前端的处理: 2.后台 1.)添加过滤器,哪里用放哪里 2.)需要验证的方法上直接添加过滤器即可 大功告成 以下为过滤器代码块 /// <summary>/// ajax中加上Anti ...

  5. python获取某路径下,某种特定类型的文件名称,os.walk(路径)生成器;os.listdir(路径),os.path.splitext(名称),os.path.join(路径,名称),os.path.isdir(路径\名称)

    #获取某文件夹下制定类型文件# import os# def filep(fp):# l=[]# a=os.walk(fp) #生成器# for nowp,sonp,oth in a: #当前目录,子 ...

  6. django update_or_create

    update_or_create question.votes.update_or_create(user=request.user, defaults={"value": val ...

  7. 剑指Offer-8.跳台阶(C++/Java)

    题目: 一只青蛙一次可以跳上1级台阶,也可以跳上2级.求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果). 分析: 实际上就是斐波那契数列的一个应用,青蛙跳上n级台阶的跳法数等于跳 ...

  8. JDOJ1178:铺地板II

    JDOJ1178:铺地板II https://neooj.com/oldoj/problem.php?id=1178 题目描述 用1 x 1和2 x 2的磁砖不重叠地铺满N x 3的地板,共有多少种方 ...

  9. jQuery3.0+报错Uncaught TypeError: e.indexOf is not a function

    jQuery3.0+报错Uncaught TypeError: e.indexOf is not a function 使用.load()绑定事件时报错,Uncaught TypeError: e.i ...

  10. ESP8266 LUA脚本语言开发: 外设篇-GPIO中断检测

    https://nodemcu.readthedocs.io/en/master/modules/gpio/#gpiomode 测试引脚 GPIO0 gpio.mode(,gpio.INT) func ...