参考答案:
关于axios
的基本使用,上篇文章已经有所涉及,这里再稍微回顾下:
发送请求
1import axios from 'axios'; 2 3axios(config) // 直接传入配置 4axios(url[, config]) // 传入url和配置 5axios[method](url[, option]) // 直接调用请求方式方法,传入url和配置 6axios[method](url[, data[, option]]) // 直接调用请求方式方法,传入data、url和配置 7axios.request(option) // 调用 request 方法 8 9const axiosInstance = axios.create(config) 10// axiosInstance 也具有以上 axios 的能力 11 12axios.all([axiosInstance1, axiosInstance2]).then(axios.spread(response1, response2)) 13// 调用 all 和传入 spread 回调 14
请求拦截器
1axios.interceptors.request.use(function (config) { 2 // 这里写发送请求前处理的代码 3 return config; 4}, function (error) { 5 // 这里写发送请求错误相关的代码 6 return Promise.reject(error); 7});
响应拦截器
1axios.interceptors.response.use(function (response) { 2 // 这里写得到响应数据后处理的代码 3 return response; 4}, function (error) { 5 // 这里写得到错误响应处理的代码 6 return Promise.reject(error); 7});
取消请求
1// 方式一 2const CancelToken = axios.CancelToken; 3const source = CancelToken.source(); 4 5axios.get('xxxx', { 6 cancelToken: source.token 7}) 8// 取消请求 (请求原因是可选的) 9source.cancel('主动取消请求'); 10 11// 方式二 12const CancelToken = axios.CancelToken; 13let cancel; 14 15axios.get('xxxx', { 16 cancelToken: new CancelToken(function executor(c) { 17 cancel = c; 18 }) 19}); 20cancel('主动取消请求');
构建一个Axios
构造函数,核心代码为request
1class Axios { 2 constructor() { 3 4 } 5 6 request(config) { 7 return new Promise(resolve => { 8 const {url = '', method = 'get', data = {}} = config; 9 // 发送ajax请求 10 const xhr = new XMLHttpRequest(); 11 xhr.open(method, url, true); 12 xhr.onload = function() { 13 console.log(xhr.responseText) 14 resolve(xhr.responseText); 15 } 16 xhr.send(data); 17 }) 18 } 19}
导出axios
实例
1// 最终导出axios的方法,即实例的request方法 2function CreateAxiosFn() { 3 let axios = new Axios(); 4 let req = axios.request.bind(axios); 5 return req; 6} 7 8// 得到最后的全局变量axios 9let axios = CreateAxiosFn();
上述就已经能够实现axios({ })
这种方式的请求
下面是来实现下axios.method()
这种形式的请求
1// 定义get,post...方法,挂在到Axios原型上 2const methodsArr = ['get', 'delete', 'head', 'options', 'put', 'patch', 'post']; 3methodsArr.forEach(met => { 4 Axios.prototype[met] = function() { 5 console.log('执行'+met+'方法'); 6 // 处理单个方法 7 if (['get', 'delete', 'head', 'options'].includes(met)) { // 2个参数(url[, config]) 8 return this.request({ 9 method: met, 10 url: arguments[0], 11 ...arguments[1] || {} 12 }) 13 } else { // 3个参数(url[,data[,config]]) 14 return this.request({ 15 method: met, 16 url: arguments[0], 17 data: arguments[1] || {}, 18 ...arguments[2] || {} 19 }) 20 } 21 22 } 23})
将Axios.prototype
上的方法搬运到request
上
首先实现个工具类,实现将b
方法混入到a
,并且修改this
指向
1const utils = { 2 extend(a,b, context) { 3 for(let key in b) { 4 if (b.hasOwnProperty(key)) { 5 if (typeof b[key] === 'function') { 6 a[key] = b[key].bind(context); 7 } else { 8 a[key] = b[key] 9 } 10 } 11 12 } 13 } 14}
修改导出的方法
1function CreateAxiosFn() { 2 let axios = new Axios(); 3 4 let req = axios.request.bind(axios); 5 // 增加代码 6 utils.extend(req, Axios.prototype, axios) 7 8 return req; 9}
构建拦截器的构造函数
1class InterceptorsManage { 2 constructor() { 3 this.handlers = []; 4 } 5 6 use(fullfield, rejected) { 7 this.handlers.push({ 8 fullfield, 9 rejected 10 }) 11 } 12}
实现axios.interceptors.response.use
和axios.interceptors.request.use
1class Axios { 2 constructor() { 3 // 新增代码 4 this.interceptors = { 5 request: new InterceptorsManage, 6 response: new InterceptorsManage 7 } 8 } 9 10 request(config) { 11 ... 12 } 13}
执行语句axios.interceptors.response.use
和axios.interceptors.request.use
的时候,实现获取axios
实例上的interceptors
对象,然后再获取response
或request
拦截器,再执行对应的拦截器的use
方法
把Axios
上的方法和属性搬到request
过去
1function CreateAxiosFn() { 2 let axios = new Axios(); 3 4 let req = axios.request.bind(axios); 5 // 混入方法, 处理axios的request方法,使之拥有get,post...方法 6 utils.extend(req, Axios.prototype, axios) 7 // 新增代码 8 utils.extend(req, axios) 9 return req; 10}
现在request
也有了interceptors
对象,在发送请求的时候,会先获取request
拦截器的handlers
的方法来执行
首先将执行ajax
的请求封装成一个方法
1request(config) { 2 this.sendAjax(config) 3} 4sendAjax(config){ 5 return new Promise(resolve => { 6 const {url = '', method = 'get', data = {}} = config; 7 // 发送ajax请求 8 console.log(config); 9 const xhr = new XMLHttpRequest(); 10 xhr.open(method, url, true); 11 xhr.onload = function() { 12 console.log(xhr.responseText) 13 resolve(xhr.responseText); 14 }; 15 xhr.send(data); 16 }) 17}
获得handlers
中的回调
1request(config) { 2 // 拦截器和请求组装队列 3 let chain = [this.sendAjax.bind(this), undefined] // 成对出现的,失败回调暂时不处理 4 5 // 请求拦截 6 this.interceptors.request.handlers.forEach(interceptor => { 7 chain.unshift(interceptor.fullfield, interceptor.rejected) 8 }) 9 10 // 响应拦截 11 this.interceptors.response.handlers.forEach(interceptor => { 12 chain.push(interceptor.fullfield, interceptor.rejected) 13 }) 14 15 // 执行队列,每次执行一对,并给promise赋最新的值 16 let promise = Promise.resolve(config); 17 while(chain.length > 0) { 18 promise = promise.then(chain.shift(), chain.shift()) 19 } 20 return promise; 21}
chains
大概是['fulfilled1','reject1','fulfilled2','reject2','this.sendAjax','undefined','fulfilled2','reject2','fulfilled1','reject1']
这种形式
这样就能够成功实现一个简易版axios
首先看看目录结构
axios
发送请求有很多实现的方法,实现入口文件为axios.js
1function createInstance(defaultConfig) { 2 var context = new Axios(defaultConfig); 3 4 // instance指向了request方法,且上下文指向context,所以可以直接以 instance(option) 方式调用 5 // Axios.prototype.request 内对第一个参数的数据类型判断,使我们能够以 instance(url, option) 方式调用 6 var instance = bind(Axios.prototype.request, context); 7 8 // 把Axios.prototype上的方法扩展到instance对象上, 9 // 并指定上下文为context,这样执行Axios原型链上的方法时,this会指向context 10 utils.extend(instance, Axios.prototype, context); 11 12 // Copy context to instance 13 // 把context对象上的自身属性和方法扩展到instance上 14 // 注:因为extend内部使用的forEach方法对对象做for in 遍历时,只遍历对象本身的属性,而不会遍历原型链上的属性 15 // 这样,instance 就有了 defaults、interceptors 属性。 16 utils.extend(instance, context); 17 return instance; 18} 19 20// Create the default instance to be exported 创建一个由默认配置生成的axios实例 21var axios = createInstance(defaults); 22 23// Factory for creating new instances 扩展axios.create工厂函数,内部也是 createInstance 24axios.create = function create(instanceConfig) { 25 return createInstance(mergeConfig(axios.defaults, instanceConfig)); 26}; 27 28// Expose all/spread 29axios.all = function all(promises) { 30 return Promise.all(promises); 31}; 32 33axios.spread = function spread(callback) { 34 return function wrap(arr) { 35 return callback.apply(null, arr); 36 }; 37}; 38module.exports = axios;
主要核心是 Axios.prototype.request
,各种请求方式的调用实现都是在 request
内部实现的, 简单看下 request
的逻辑
1Axios.prototype.request = function request(config) { 2 // Allow for axios('example/url'[, config]) a la fetch API 3 // 判断 config 参数是否是 字符串,如果是则认为第一个参数是 URL,第二个参数是真正的config 4 if (typeof config === 'string') { 5 config = arguments[1] || {}; 6 // 把 url 放置到 config 对象中,便于之后的 mergeConfig 7 config.url = arguments[0]; 8 } else { 9 // 如果 config 参数是否是 字符串,则整体都当做config 10 config = config || {}; 11 } 12 // 合并默认配置和传入的配置 13 config = mergeConfig(this.defaults, config); 14 // 设置请求方法 15 config.method = config.method ? config.method.toLowerCase() : 'get'; 16 /* 17 something... 此部分会在后续拦截器单独讲述 18 */ 19}; 20 21// 在 Axios 原型上挂载 'delete', 'get', 'head', 'options' 且不传参的请求方法,实现内部也是 request 22utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) { 23 Axios.prototype[method] = function(url, config) { 24 return this.request(utils.merge(config || {}, { 25 method: method, 26 url: url 27 })); 28 }; 29}); 30 31// 在 Axios 原型上挂载 'post', 'put', 'patch' 且传参的请求方法,实现内部同样也是 request 32utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { 33 Axios.prototype[method] = function(url, data, config) { 34 return this.request(utils.merge(config || {}, { 35 method: method, 36 url: url, 37 data: data 38 })); 39 }; 40});
request
入口参数为config
,可以说config
贯彻了axios
的一生
axios
中的 config
主要分布在这几个地方:
defaults.js
config.method
默认为 get
createInstance
方法创建 axios
实例,传入的config
request
方法,传入的 config
1// axios.js 2// 创建一个由默认配置生成的axios实例 3var axios = createInstance(defaults); 4 5// 扩展axios.create工厂函数,内部也是 createInstance 6axios.create = function create(instanceConfig) { 7 return createInstance(mergeConfig(axios.defaults, instanceConfig)); 8}; 9 10// Axios.js 11// 合并默认配置和传入的配置 12config = mergeConfig(this.defaults, config); 13// 设置请求方法 14config.method = config.method ? config.method.toLowerCase() : 'get'; 15
从源码中,可以看到优先级:默认配置对象default
< method:get
< Axios
的实例属性this.default
< request
参数
下面重点看看request
方法
1Axios.prototype.request = function request(config) { 2 /* 3 先是 mergeConfig ... 等,不再阐述 4 */ 5 // Hook up interceptors middleware 创建拦截器链. dispatchRequest 是重中之重,后续重点 6 var chain = [dispatchRequest, undefined]; 7 8 // push各个拦截器方法 注意:interceptor.fulfilled 或 interceptor.rejected 是可能为undefined 9 this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { 10 // 请求拦截器逆序 注意此处的 forEach 是自定义的拦截器的forEach方法 11 chain.unshift(interceptor.fulfilled, interceptor.rejected); 12 }); 13 14 this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { 15 // 响应拦截器顺序 注意此处的 forEach 是自定义的拦截器的forEach方法 16 chain.push(interceptor.fulfilled, interceptor.rejected); 17 }); 18 19 // 初始化一个promise对象,状态为resolved,接收到的参数为已经处理合并过的config对象 20 var promise = Promise.resolve(config); 21 22 // 循环拦截器的链 23 while (chain.length) { 24 promise = promise.then(chain.shift(), chain.shift()); // 每一次向外弹出拦截器 25 } 26 // 返回 promise 27 return promise; 28};
拦截器interceptors
是在构建axios
实例化的属性
1function Axios(instanceConfig) { 2 this.defaults = instanceConfig; 3 this.interceptors = { 4 request: new InterceptorManager(), // 请求拦截 5 response: new InterceptorManager() // 响应拦截 6 }; 7}
InterceptorManager
构造函数
1// 拦截器的初始化 其实就是一组钩子函数 2function InterceptorManager() { 3 this.handlers = []; 4} 5 6// 调用拦截器实例的use时就是往钩子函数中push方法 7InterceptorManager.prototype.use = function use(fulfilled, rejected) { 8 this.handlers.push({ 9 fulfilled: fulfilled, 10 rejected: rejected 11 }); 12 return this.handlers.length - 1; 13}; 14 15// 拦截器是可以取消的,根据use的时候返回的ID,把某一个拦截器方法置为null 16// 不能用 splice 或者 slice 的原因是 删除之后 id 就会变化,导致之后的顺序或者是操作不可控 17InterceptorManager.prototype.eject = function eject(id) { 18 if (this.handlers[id]) { 19 this.handlers[id] = null; 20 } 21}; 22 23// 这就是在 Axios的request方法中 中循环拦截器的方法 forEach 循环执行钩子函数 24InterceptorManager.prototype.forEach = function forEach(fn) { 25 utils.forEach(this.handlers, function forEachHandler(h) { 26 if (h !== null) { 27 fn(h); 28 } 29 }); 30}
请求拦截器方法是被 unshift
到拦截器中,响应拦截器是被push
到拦截器中的。最终它们会拼接上一个叫dispatchRequest
的方法被后续的 promise
顺序执行
1var utils = require('./../utils'); 2var transformData = require('./transformData'); 3var isCancel = require('../cancel/isCancel'); 4var defaults = require('../defaults'); 5var isAbsoluteURL = require('./../helpers/isAbsoluteURL'); 6var combineURLs = require('./../helpers/combineURLs'); 7 8// 判断请求是否已被取消,如果已经被取消,抛出已取消 9function throwIfCancellationRequested(config) { 10 if (config.cancelToken) { 11 config.cancelToken.throwIfRequested(); 12 } 13} 14 15module.exports = function dispatchRequest(config) { 16 throwIfCancellationRequested(config); 17 18 // 如果包含baseUrl, 并且不是config.url绝对路径,组合baseUrl以及config.url 19 if (config.baseURL && !isAbsoluteURL(config.url)) { 20 // 组合baseURL与url形成完整的请求路径 21 config.url = combineURLs(config.baseURL, config.url); 22 } 23 24 config.headers = config.headers || {}; 25 26 // 使用/lib/defaults.js中的transformRequest方法,对config.headers和config.data进行格式化 27 // 比如将headers中的Accept,Content-Type统一处理成大写 28 // 比如如果请求正文是一个Object会格式化为JSON字符串,并添加application/json;charset=utf-8的Content-Type 29 // 等一系列操作 30 config.data = transformData( 31 config.data, 32 config.headers, 33 config.transformRequest 34 ); 35 36 // 合并不同配置的headers,config.headers的配置优先级更高 37 config.headers = utils.merge( 38 config.headers.common || {}, 39 config.headers[config.method] || {}, 40 config.headers || {} 41 ); 42 43 // 删除headers中的method属性 44 utils.forEach( 45 ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'], 46 function cleanHeaderConfig(method) { 47 delete config.headers[method]; 48 } 49 ); 50 51 // 如果config配置了adapter,使用config中配置adapter的替代默认的请求方法 52 var adapter = config.adapter || defaults.adapter; 53 54 // 使用adapter方法发起请求(adapter根据浏览器环境或者Node环境会有不同) 55 return adapter(config).then( 56 // 请求正确返回的回调 57 function onAdapterResolution(response) { 58 // 判断是否以及取消了请求,如果取消了请求抛出以取消 59 throwIfCancellationRequested(config); 60 61 // 使用/lib/defaults.js中的transformResponse方法,对服务器返回的数据进行格式化 62 // 例如,使用JSON.parse对响应正文进行解析 63 response.data = transformData( 64 response.data, 65 response.headers, 66 config.transformResponse 67 ); 68 69 return response; 70 }, 71 // 请求失败的回调 72 function onAdapterRejection(reason) { 73 if (!isCancel(reason)) { 74 throwIfCancellationRequested(config); 75 76 if (reason && reason.response) { 77 reason.response.data = transformData( 78 reason.response.data, 79 reason.response.headers, 80 config.transformResponse 81 ); 82 } 83 } 84 return Promise.reject(reason); 85 } 86 ); 87};
再来看看axios
是如何实现取消请求的,实现文件在CancelToken.js
1function CancelToken(executor) { 2 if (typeof executor !== 'function') { 3 throw new TypeError('executor must be a function.'); 4 } 5 // 在 CancelToken 上定义一个 pending 状态的 promise ,将 resolve 回调赋值给外部变量 resolvePromise 6 var resolvePromise; 7 this.promise = new Promise(function promiseExecutor(resolve) { 8 resolvePromise = resolve; 9 }); 10 11 var token = this; 12 // 立即执行 传入的 executor函数,将真实的 cancel 方法通过参数传递出去。 13 // 一旦调用就执行 resolvePromise 即前面的 promise 的 resolve,就更改promise的状态为 resolve。 14 // 那么xhr中定义的 CancelToken.promise.then方法就会执行, 从而xhr内部会取消请求 15 executor(function cancel(message) { 16 // 判断请求是否已经取消过,避免多次执行 17 if (token.reason) { 18 return; 19 } 20 token.reason = new Cancel(message); 21 resolvePromise(token.reason); 22 }); 23} 24 25CancelToken.source = function source() { 26 // source 方法就是返回了一个 CancelToken 实例,与直接使用 new CancelToken 是一样的操作 27 var cancel; 28 var token = new CancelToken(function executor(c) { 29 cancel = c; 30 }); 31 // 返回创建的 CancelToken 实例以及取消方法 32 return { 33 token: token, 34 cancel: cancel 35 }; 36};
实际上取消请求的操作是在 xhr.js
中也有响应的配合的
1if (config.cancelToken) { 2 config.cancelToken.promise.then(function onCanceled(cancel) { 3 if (!request) { 4 return; 5 } 6 // 取消请求 7 request.abort(); 8 reject(cancel); 9 }); 10}
巧妙的地方在 CancelToken
中 executor
函数,通过resolve
函数的传递与执行,控制promise
的状态
最近更新时间:2024-08-10