axios是一个基于promise的http库,支持浏览器和node端,最近我在做beauty-we的api设计,研读一个成熟的http库势在必行,axios功能完整、api简洁、注释清晰,再适合不过,现在就让我们开始吧~
准备工作
clone项目,打开dist文件夹,里面有axios.js和axios.min.js,看一下axios.js的第一段:
- (function webpackUniversalModuleDefinition(root, factory) {
- if (typeof exports === 'object' && typeof module === 'object') {
- module.exports = factory();
- } else if (typeof define === 'function' && define.amd) {
- define([], factory);
- } else if (typeof exports === 'object') {
- exports.axios = factory();
- } else {
- root.axios = factory();
- }
- })(this, function() {...我是主代码部分...});
最外层是一个立即执行函数(IIFE),传入参数this和构造函数factory,区分了4种情况:
- exports和module都存在时,cmd模式;
- define和define.amd都存在时,amd模式;
- 只有exports存在时;
- 既不是cmd也不是cmd模式时,挂载在传入的宿主对象上。
我比较了其他3个类库的处理(underscore/jquery/vue):
underscore
- (function() {
- var root = typeof self == 'object' && self.self === self && self ||typeof global == 'object' && global.global === global && global ||this ||{};
-
- if (typeof exports != 'undefined' && !exports.nodeType) {
- if (typeof module != 'undefined' && !module.nodeType && module.exports) {
- exports = module.exports = _;
- }
- exports._ = _;
- } else {
- root._ = _;
- }
-
- // ....这里是主代码部分...
- if (typeof define == 'function' && define.amd) {
- define('underscore', [], function() {
- return _;
- });
- }
- }());
jquery
- (function( global, factory ) {
- if ( typeof module === "object" && typeof module.exports === "object" ) {
- module.exports = global.document ?
- factory( global, true ) :
- function( w ) {
- if ( !w.document ) {
- throw new Error( "jQuery requires a window with a document" );
- }
- return factory( w );
- };
- } else {
- factory( global );
- }
- if ( typeof define === "function" && define.amd ) {
- define( "jquery", [], function() {
- return jQuery;
- } );
- }
-
- })(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
- // ...这里是主代码部分
- })
vue
-
- (function (global, factory) {
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
- typeof define === 'function' && define.amd ? define(factory) :
- (global.Vue = factory());
- }(this, (function () {
- // ...这里是主代码部分...
- }))
4个库中,
- underscore和axios考虑了4种环境情况,vue和jquery无视了exports存在但没有module的情况(这种情况什么时候出现我不清楚,知道的人求指出);环境判断vue用了3目运算符,看起来又干净又漂亮(其实这好像并不重要,压缩后大家都一样~);对amd的支持underscore和jquery都放在了代码的尾部,打补丁的痕迹好明显;
- jquery强调了document一定存在,否则会抛出错误,axios和vue只是传入了this,underscore则认真区分了browser的window,server端的global,其他端的this,自创了一个self统一之,这意味着后三者可以存活在更普遍的环境中(比如axios in web worker,比如weex),underscore的做法最严谨;
- 除了underscore,其他3者都把主代码放在了IIFE的参数里,而underscore是直接写在IIFE里,我个人还是喜欢用那个叫factory的函数。
看整体
啰嗦了那么多之后,正式开始看axios啦。根据package.json里的npm run dev的指向,我们打开lib文件夹里的axios.js,这就是全局入口了。
./lib/axios.js只做了两件事:
- 创建一个axios对象
- 暴露一些拓展方法出去
先看axios对象的创建:
- var axios = createInstance(defaults);
-
- function createInstance(defaultConfig) {
- // 一切的开始,new一个实例出来
- var context = new Axios(defaultConfig);
-
- var instance = bind(Axios.prototype.request, context);
-
- utils.extend(instance, Axios.prototype, context);
-
- utils.extend(instance, context);
-
- return instance;
- }
创建实例axios用了4步,第2步开始就比较奇怪了:
var instance = bind(Axios.prototype.request, context);
看一下bind函数:
- function bind(fn, thisArg) {
- return function wrap() {
- var args = new Array(arguments.length);
- for (var i = 0; i < args.length; i++) {
- args[i] = arguments[i];
- }
- return fn.apply(thisArg, args);
- };
- }
它返回一个wrap()函数,其中执行了参数1的函数,绑定了参数2的上下文,也就是说,instance是一个叫wrap的函数,它里面执行了Axios原型对象上的request方法,同时绑定了最开始创建的那个实例的上下文;
再看下一行:
utils.extend(instance, Axios.prototype, context);
看一下extend函数:
- function extend(a, b, thisArg) {
- forEach(b, function assignValue(val, key) {
- if (thisArg && typeof val === 'function') {
- a[key] = bind(val, thisArg);
- } else {
- a[key] = val;
- }
- });
- return a;
- }
它承担了两个任务,当参数3存在时,它循环参数2,把参数2 上的属性是函数类型的都用bind的方式挂载到参数1身上;否则的话,它实现了简单的把参数2的复制给参数1;
那么看最后一行就知道了,3-4实现的是把Axios原型上的函数属性都挂载到instance,这是第2行的一个循环版本,最后把axios实例复制给instance,形成一个真正的instance,此时,这个instance具备Axios实例的静态属性(复制来的),Axios.prototype上所有函数方法(通过bind,返回一个wrap(),里面调用Axios.prototype上的方法),instance本身还是一个wrap版本的Axios.prototype.request方法。
这时我好想问一个:为什么这么做。。
思考好久找到突破口,就是最奇怪的最后一点,axios是一个wrap函数,包含了request,这其实是因为axios支持的两种调用方式:axios({config}).then()
和axios.get('url').then()
前者确定了axios自身作为一个函数的特性,同时,它还需要具有get/post等一堆方法,于是我翻到的commit最初的版本是:
- var defaultInstance = new Axios();
-
- var axios = module.exports = bind(Axios.prototype.request, defaultInstance);
这里还少了把defaultInstance上的静态属性复制给axios的过程,当时Axios上的静态属性还很少,只有defaults,被手动挂载给axios:axios.defaults=defaults;
,
随着Axios添加了InterceptorManager拦截器,进化成了第二版本:
- var defaultInstance = new Axios();
-
- var axios = module.exports = bind(Axios.prototype.request, defaultInstance);
-
- axios.defaults=defaults;
- axios.interceptors = defaultInstance.interceptors;
但是有人发现了一个bug,因为axios自带create方法,可以自定义一个实例,而create内写的却是极度简陋的:
- axios.create = function (defaultConfig) {
- return new Axios(defaultConfig);
- };
大家发现axios.create出来的和axios比较后api少很多啊,于是终极版本的构建函数createInstance诞生了:
- function createInstance(defaultConfig) {
- // 一切的开始,new一个实例出来,然而考虑到它不是最终对象,只能称之为上下文
- var context = new Axios(defaultConfig);
- // 这是最后返回的实例,本身是一个request函数
- var instance = bind(Axios.prototype.request, context);
- // 为了拓展性,一口气把原型对象上所有的方法都拷一份
- utils.extend(instance, Axios.prototype, context);
-
- // 把实例的静态属性复制过来
- utils.extend(instance, context);
-
- return instance;
- }
终于创建完axios对象,接下来是暴露一些api出去:
- // Factory for creating new instances
- axios.create = function create(instanceConfig) {
- return createInstance(mergeConfig(axios.defaults, instanceConfig));
- };
- // Expose Axios class to allow class inheritance
- axios.Axios = Axios;
- axios.all = function all(promises) {
- return Promise.all(promises);
- };
- axios.spread =function spread(callback) {
- return function wrap(arr) {
- return callback.apply(null, arr);
- };
- }
- axios.Cancel...
- axios.CancelToken...
- axios.isCancel...
入口文件的分析就到此结束了。
core Axios
开始研究核心代码Axios这个类;
首先是构造函数:
- function Axios(instanceConfig) {
- this.defaults = instanceConfig;
- this.interceptors = {
- request: new InterceptorManager(),
- response: new InterceptorManager()
- };
- }
看完上面的内容大家应该有点印象,axios上挂了defaults和interceptors,defaults是默认的config配置,interceptors顾名思义就是拦截器,目测包含了request和response两种类型。
接下来看个简单的部分:
- utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
- /*eslint func-names:0*/
- Axios.prototype[method] = function(url, config) {
- return this.request(utils.merge(config || {}, {
- method: method,
- url: url
- }));
- };
- });
- utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
- /*eslint func-names:0*/
- Axios.prototype[method] = function(url, data, config) {
- return this.request(utils.merge(config || {}, {
- method: method,
- url: url,
- data: data
- }));
- };
- });
axios把7种请求都转向了request方法,根据是否要带data写了2个循环,看起来还挺简洁的。
接下来看最核心的request代码,这里的处理很巧妙很优雅:
-
- Axios.prototype.request = function request(config) {
- // ...我是很多config处理...此时不关心
-
- // 创建了一个事件数组,第一位的是发送请求的动作,这是默认状态
- var chain = [dispatchRequest, undefined];
- // 创建了一个promise对象
- var promise = Promise.resolve(config);
- // 循环request拦截器,有的话就添加到chain的头部
- this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
- chain.unshift(interceptor.fulfilled, interceptor.rejected);
- });
- // 循环response拦截器,有的话就添加到chain的尾部
- this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
- chain.push(interceptor.fulfilled, interceptor.rejected);
- });
- // 利用while循环,执行完所有事件
- while (chain.length) {
- promise = promise.then(chain.shift(), chain.shift());
- }
- // 返回最终的promise对象
- return promise;
- }
核心axios对象到此已看完,下一节计划分析dispatchRequest---adapter,真正发送请求的部分。喜欢就点个赞吧
参考文章: