面试官:你了解axios的原理吗?有看过它的源码吗?
一、axios的使用 关于axios
的基本使用,上篇文章已经有所涉及,这里再稍微回顾下:
发送请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import axios from 'axios' ;axios (config) axios (url[, config]) axios[method](url[, option]) axios[method](url[, data[, option]]) axios.request (option) const axiosInstance = axios.create (config)axios.all ([axiosInstance1, axiosInstance2]).then (axios.spread (response1, response2))
请求拦截器
1 2 3 4 5 6 7 axios.interceptors .request .use (function (config ) { return config; }, function (error ) { return Promise .reject (error); });
响应拦截器
1 2 3 4 5 6 7 axios.interceptors .response .use (function (response ) { return response; }, function (error ) { return Promise .reject (error); });
取消请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const CancelToken = axios.CancelToken ;const source = CancelToken .source ();axios.get ('xxxx' , { cancelToken : source.token }) source.cancel ('主动取消请求' ); const CancelToken = axios.CancelToken ;let cancel;axios.get ('xxxx' , { cancelToken : new CancelToken (function executor (c ) { cancel = c; }) }); cancel ('主动取消请求' );
二、实现一个简易版axios 构建一个Axios
构造函数,核心代码为request
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Axios { constructor ( ) { } request (config ) { return new Promise (resolve => { const {url = '' , method = 'get' , data = {}} = config; const xhr = new XMLHttpRequest (); xhr.open (method, url, true ); xhr.onload = function ( ) { console .log (xhr.responseText ) resolve (xhr.responseText ); } xhr.send (data); }) } }
导出axios
实例
1 2 3 4 5 6 7 8 9 function CreateAxiosFn ( ) { let axios = new Axios (); let req = axios.request .bind (axios); return req; } let axios = CreateAxiosFn ();
上述就已经能够实现axios({ })
这种方式的请求
下面是来实现下axios.method()
这种形式的请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const methodsArr = ['get' , 'delete' , 'head' , 'options' , 'put' , 'patch' , 'post' ];methodsArr.forEach (met => { Axios .prototype [met] = function ( ) { console .log ('执行' +met+'方法' ); if (['get' , 'delete' , 'head' , 'options' ].includes (met)) { return this .request ({ method : met, url : arguments [0 ], ...arguments [1 ] || {} }) } else { return this .request ({ method : met, url : arguments [0 ], data : arguments [1 ] || {}, ...arguments [2 ] || {} }) } } })
将Axios.prototype
上的方法搬运到request
上
首先实现个工具类,实现将b
方法混入到a
,并且修改this
指向
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const utils = { extend (a,b, context ) { for (let key in b) { if (b.hasOwnProperty (key)) { if (typeof b[key] === 'function' ) { a[key] = b[key].bind (context); } else { a[key] = b[key] } } } } }
修改导出的方法
1 2 3 4 5 6 7 8 9 function CreateAxiosFn ( ) { let axios = new Axios (); let req = axios.request .bind (axios); utils.extend (req, Axios .prototype , axios) return req; }
构建拦截器的构造函数
1 2 3 4 5 6 7 8 9 10 11 12 class InterceptorsManage { constructor ( ) { this .handlers = []; } use (fullfield, rejected ) { this .handlers .push ({ fullfield, rejected }) } }
实现axios.interceptors.response.use
和axios.interceptors.request.use
1 2 3 4 5 6 7 8 9 10 11 12 13 class Axios { constructor ( ) { this .interceptors = { request : new InterceptorsManage , response : new InterceptorsManage } } request (config ) { ... } }
执行语句axios.interceptors.response.use
和axios.interceptors.request.use
的时候,实现获取axios
实例上的interceptors
对象,然后再获取response
或request
拦截器,再执行对应的拦截器的use
方法
把Axios
上的方法和属性搬到request
过去
1 2 3 4 5 6 7 8 9 10 function CreateAxiosFn ( ) { let axios = new Axios (); let req = axios.request .bind (axios); utils.extend (req, Axios .prototype , axios) utils.extend (req, axios) return req; }
现在request
也有了interceptors
对象,在发送请求的时候,会先获取request
拦截器的handlers
的方法来执行
首先将执行ajax
的请求封装成一个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 request (config ) { this .sendAjax (config) } sendAjax (config ){ return new Promise (resolve => { const {url = '' , method = 'get' , data = {}} = config; console .log (config); const xhr = new XMLHttpRequest (); xhr.open (method, url, true ); xhr.onload = function ( ) { console .log (xhr.responseText ) resolve (xhr.responseText ); }; xhr.send (data); }) }
获得handlers
中的回调
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 request (config ) { let chain = [this .sendAjax .bind (this ), undefined ] this .interceptors .request .handlers .forEach (interceptor => { chain.unshift (interceptor.fullfield , interceptor.rejected ) }) this .interceptors .response .handlers .forEach (interceptor => { chain.push (interceptor.fullfield , interceptor.rejected ) }) let promise = Promise .resolve (config); while (chain.length > 0 ) { promise = promise.then (chain.shift (), chain.shift ()) } return promise; }
chains
大概是['fulfilled1','reject1','fulfilled2','reject2','this.sendAjax','undefined','fulfilled2','reject2','fulfilled1','reject1']
这种形式
这样就能够成功实现一个简易版axios
三、源码分析 首先看看目录结构
axios
发送请求有很多实现的方法,实现入口文件为axios.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 function createInstance (defaultConfig ) { var context = new Axios (defaultConfig); var instance = bind (Axios .prototype .request , context); utils.extend (instance, Axios .prototype , context); utils.extend (instance, context); return instance; } var axios = createInstance (defaults);axios.create = function create (instanceConfig ) { return createInstance (mergeConfig (axios.defaults , instanceConfig)); }; axios.all = function all (promises ) { return Promise .all (promises); }; axios.spread = function spread (callback ) { return function wrap (arr ) { return callback.apply (null , arr); }; }; module .exports = axios;
主要核心是 Axios.prototype.request
,各种请求方式的调用实现都是在 request
内部实现的, 简单看下 request
的逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 Axios .prototype .request = function request (config ) { if (typeof config === 'string' ) { config = arguments [1 ] || {}; config.url = arguments [0 ]; } else { config = config || {}; } config = mergeConfig (this .defaults , config); config.method = config.method ? config.method .toLowerCase () : 'get' ; }; 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 })); }; }); 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 })); }; });
request
入口参数为config
,可以说config
贯彻了axios
的一生
axios
中的 config
主要分布在这几个地方:
默认配置 defaults.js
config.method
默认为 get
调用 createInstance
方法创建 axios
实例,传入的config
直接或间接调用 request
方法,传入的 config
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var axios = createInstance (defaults);axios.create = function create (instanceConfig ) { return createInstance (mergeConfig (axios.defaults , instanceConfig)); }; config = mergeConfig (this .defaults , config); config.method = config.method ? config.method .toLowerCase () : 'get' ;
从源码中,可以看到优先级:默认配置对象default
< method:get
< Axios
的实例属性this.default
< request
参数
下面重点看看request
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 Axios .prototype .request = function request (config ) { var chain = [dispatchRequest, undefined ]; 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 ); }); var promise = Promise .resolve (config); while (chain.length ) { promise = promise.then (chain.shift (), chain.shift ()); } return promise; };
拦截器interceptors
是在构建axios
实例化的属性
1 2 3 4 5 6 7 function Axios (instanceConfig ) { this .defaults = instanceConfig; this .interceptors = { request : new InterceptorManager (), response : new InterceptorManager () }; }
InterceptorManager
构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 function InterceptorManager ( ) { this .handlers = []; } InterceptorManager .prototype .use = function use (fulfilled, rejected ) { this .handlers .push ({ fulfilled : fulfilled, rejected : rejected }); return this .handlers .length - 1 ; }; InterceptorManager .prototype .eject = function eject (id ) { if (this .handlers [id]) { this .handlers [id] = null ; } }; InterceptorManager .prototype .forEach = function forEach (fn ) { utils.forEach (this .handlers , function forEachHandler (h ) { if (h !== null ) { fn (h); } }); }
请求拦截器方法是被 unshift
到拦截器中,响应拦截器是被push
到拦截器中的。最终它们会拼接上一个叫dispatchRequest
的方法被后续的 promise
顺序执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 var utils = require ('./../utils' );var transformData = require ('./transformData' );var isCancel = require ('../cancel/isCancel' );var defaults = require ('../defaults' );var isAbsoluteURL = require ('./../helpers/isAbsoluteURL' );var combineURLs = require ('./../helpers/combineURLs' );function throwIfCancellationRequested (config ) { if (config.cancelToken ) { config.cancelToken .throwIfRequested (); } } module .exports = function dispatchRequest (config ) { throwIfCancellationRequested (config); if (config.baseURL && !isAbsoluteURL (config.url )) { config.url = combineURLs (config.baseURL , config.url ); } config.headers = config.headers || {}; config.data = transformData ( config.data , config.headers , config.transformRequest ); config.headers = utils.merge ( config.headers .common || {}, config.headers [config.method ] || {}, config.headers || {} ); utils.forEach ( ['delete' , 'get' , 'head' , 'post' , 'put' , 'patch' , 'common' ], function cleanHeaderConfig (method ) { delete config.headers [method]; } ); var adapter = config.adapter || defaults.adapter ; return adapter (config).then ( function onAdapterResolution (response ) { throwIfCancellationRequested (config); response.data = transformData ( response.data , response.headers , config.transformResponse ); return response; }, function onAdapterRejection (reason ) { if (!isCancel (reason)) { throwIfCancellationRequested (config); if (reason && reason.response ) { reason.response .data = transformData ( reason.response .data , reason.response .headers , config.transformResponse ); } } return Promise .reject (reason); } ); };
再来看看axios
是如何实现取消请求的,实现文件在CancelToken.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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 ) { return ; } token.reason = new Cancel (message); resolvePromise (token.reason ); }); } CancelToken .source = function source ( ) { var cancel; var token = new CancelToken (function executor (c ) { cancel = c; }); return { token : token, cancel : cancel }; };
实际上取消请求的操作是在 xhr.js
中也有响应的配合的
1 2 3 4 5 6 7 8 9 10 if (config.cancelToken ) { config.cancelToken .promise .then (function onCanceled (cancel ) { if (!request) { return ; } request.abort (); reject (cancel); }); }
巧妙的地方在 CancelToken
中 executor
函数,通过resolve
函数的传递与执行,控制promise
的状态
小结
参考文献