# 📁 lib # |—— 📁 adapters // axios主要使用的请求方法 # |—— |—— 📃 http.js // axios中node端使用的请求函数 # |—— |—— 📃 xhr.js // axios中浏览器端使用的请求函数 # |—— 📁 cancel # |—— |—— 📃 Cancel.js // 定义了,取消请求返回的信息结构 # |—— |—— 📃 CancelToken.js // 定义了用于取消请求的主要方法 # |—— |—— 📃 isCancel.js // 判断是不是取消请求的信息 # |—— 📁 core # |—— |—— 📃 Axios.js // Axios类 # |—— |—— 📃 dispatchRequest.js // 发起请求的地方 # |—— |—— 📃 InterceptorManager.js // InterceptorManager类,拦截器类 # |—— |—— 📃 mergeConfig.js // 合并配置项 # |—— |—— 📃 settle.js // 根据请求状态,处理Promise # |—— |—— 📃 createError.js // 生成指定的error # |—— |—— 📃 enhanceError.js // 指定error对象的toJSON方法 # |—— |—— 📃 transformData.js // 使用default.js中transformRequest和transformResponse对响应以及请求进行格式化 # |—— 📁 helpers # |—— |—— 📃 bind.js // 工具函数 # |—— |—— 📃 parseHeaders.js // 将getAllResponseHeaders返回的header信息转化为对象 # |—— |—— 📃 buildURL.js // 将params参数 # |—— |—— 📃 cookies.js // 封装了读取,写入,删除cookies的方法 # |—— |—— 📃 isURLSameOrigin.js // 检测当前的url与请求的url是否同源 # |—— |—— 📃 normalizeHeaderName.js // 对对象属性名的进行格式化,删除,新建符合大小写规范的属性 # |—— |—— 📃 combineURLs.js // 组合baseurl # |—— |—— 📃 isAbsoluteURL.js // 判断是否为绝对路径(指的://或//开头的为绝对路径) # |—— 📃 axios.js # |—— 📃 defaults.js // axios中默认配置 # |—— 📃 utils.js // 一些工具方法 # |—— |—— ⏹ isFormData // 判断是不是formData对象 # |—— |—— ⏹ isStandardBrowserEnv // 判断当前环境是否为标准浏览器环境 # |—— |—— ⏹ isUndefined // 判断是否为undefined # |—— |—— ⏹ merge # |—— |—— ⏹ isURLSearchParams // 判断是否为URLSearchParams对象
本文主要关注axios中主流程的源码,对于一些工具函数的实现会略过。还请见谅。若是文章中有错误的地方,还请及时指出。node
下面是axios源码中发起一个请求时代码大体的流程
CancelToken.js中定义了取消axios请求的相关行为的代码。但CancelToken.source返回的取消请求的cancel方法,使用的前提,是须要将CancelToken.source返回token的,结合到具体的请求的config中才能正常使用。
我在看axios源码以前,甚至并不知道axios能够发出的请求,因此咱们先来了解下如何在axios取消一个请求。下面是一个例子🌰ios
// axios用于取消请求的类 const CancelToken = axios.CancelToken // source方法会返回一个对象,对象包含 // { // token, 添加到请求的config,用于标识请求 // cancel, 调用cancel方法取消请求 // } const source = CancelToken.source() axios.get('/info', { cancelToken: source.token }).catch(function(error) { if (axios.isCancel(error)) { console.log('取消请求的错误') } else { // 其余错误 } }) // 调用source.cancel能够取消axios.get('/info')的请求 source.cancel('取消请求')
var Cancel = require('./Cancel'); function CancelToken(executor) { if (typeof executor !== 'function') { throw new TypeError('executor must be a function.'); } var resolvePromise; // 建立一个Promise // 在调用cancel函数前该promise会一直处于pending状态 this.promise = new Promise(function promiseExecutor(resolve) { resolvePromise = resolve; }); var token = this; executor(function cancel(message) { // 判断是否已经取消请求了 if (token.reason) { return; } // 建立取消请求的信息,并将信息添加到实例的reason属性上 token.reason = new Cancel(message); // 结束this.promise的pending状态 // 将this.promise状态设置为resolve resolvePromise(token.reason); }); } // 判断该请求是否已经被取消的方法 CancelToken.prototype.throwIfRequested = function throwIfRequested() { if (this.reason) { throw this.reason; } }; CancelToken.source = function source() { var cancel; var token = new CancelToken(function executor(c) { cancel = c; }); return { token: token, cancel: cancel }; }; module.exports = CancelToken;
🤔 看到这里,咱们仍是没法了解axios是如何取消一个请求的。由于单独使用CancelToken.source返回的cancel是没法取消一个请求的,咱们须要结合xhr.js中的代码来理解。ajax
// /lib/adapters/xhr.js request.open() // ...省略 // 若是配置了cancelToken选项 if (config.cancelToken) { // 对CancelToken中建立的Promise添加成功的回调 // 当调用CancelToken.source暴露的cancel函数时,回调会被触发 config.cancelToken.promise.then(function onCanceled(cancel) { if (!request) { return; } // 取消xhr请求 request.abort(); // 将axios返回的promise,置为reject态 reject(cancel); request = null; }); } // ...省略 request.send()
想必你们看到这里,对axios中如何请求有了一个大体的了解。咱们来总结一下,咱们经过CancelToken,建立了一个额外的PromiseA,并将PromiseA挂载到config中,同时将该PromiseA的resolve方法暴露出去。咱们在调用send方法前(发送请求前)添加对PromiseA的状态进行监听,当PromiseA的状态被修改,咱们会在PromiseA的callback中取消请求,而且将axios返回的PromiseB的状态置为reject。从而达到取消请求的目的json
xhr.js导出的xhrAdapter方法是axios在浏览器环境下使用的默认请求方法。咱们能够在配置中使用 adapter配置项对默认请求方法进行替换。
module.exports = function xhrAdapter(config) { return new Promise(function dispatchXhrRequest(resolve, reject) { var requestData = config.data; var requestHeaders = config.headers; // 判断是不是FormData对象, 若是是, 删除header的Content-Type字段,让浏览器自动设置Content-Type字段 if (utils.isFormData(requestData)) { delete requestHeaders['Content-Type']; } // 建立xtr对象 var request = new XMLHttpRequest(); // 设置http请求头中的Authorization字段 // 关于Authorization字段 // 更多内容参考https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Authorization if (config.auth) { var username = config.auth.username || ''; var password = config.auth.password || ''; // 使用btoa方法base64编码username和password requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password); } // 初始化请求方法 // open(method: 请求的http方法, url: 请求的url地址, 是否支持异步) request.open( config.method.toUpperCase(), buildURL(config.url, config.params, config.paramsSerializer), true ); // 设置超时时间 request.timeout = config.timeout; // 监听readyState状态的变化,当readyState状态为4的时候,表示ajax请求成功 request.onreadystatechange = function handleLoad() { if (!request || request.readyState !== 4) { return; } // request.status响应的数字状态码,在完成请求前数字状态码等于0 // 若是request.status出错返回的也是0,可是file协议除外,status等于0也是一个成功的请求 // 更多内容请参考 https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/status if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) { return; } // getAllResponseHeaders方法会返回全部的响应头 // 更多内容请参考 https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/getAllResponseHeaders var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null; // 若是没有设置数据响应类型(默认为“json”)或者responseType设置为text时,获取request.responseText值不然是获取request.response // responseType是一个枚举类型,手动设置返回数据的类型 更多请参考 https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/responseType // responseText是所有后端的返回数据为纯文本的值 更多请参考 https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/responseText // response为正文,response的类型取决于responseType 更多请参考 https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/response var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response; var response = { data: responseData, // 响应正文 status: request.status, // 响应状态 statusText: request.statusText, // 响应状态的文本信息 headers: responseHeaders, // 响应头 config: config, request: request }; // status >= 200 && status < 300 resolve // 不然reject settle(resolve, reject, response); request = null; }; // ajax中断时触发 request.onabort = function handleAbort() { if (!request) { return; } // 抛出Request aborted错误 reject(createError('Request aborted', config, 'ECONNABORTED', request)); request = null; }; // ajax失败时触发 request.onerror = function handleError() { // 抛出Network Error错误 reject(createError('Network Error', config, null, request)); request = null; }; // ajax请求超时时调用 request.ontimeout = function handleTimeout() { // 抛出 timeout错误 reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED', request)); request = null; }; // 判断当前是为标准浏览器环境,若是是,添加xsrf头 // 什么是xsrf header? xsrf header是用来防护CSRF攻击 // 原理是服务端生成一个XSRF-TOKEN,并保存到浏览器的cookie中,在每次请求中ajax都会将XSRF-TOKEN设置到request header中 // 服务器会比较cookie中的XSRF-TOKEN与header中XSRF-TOKEN是否一致 // 根据同源策略,非同源的网站没法读取修改本源的网站cookie,避免了伪造cookie if (utils.isStandardBrowserEnv()) { var cookies = require('./../helpers/cookies'); // withCredentials设置跨域请求中是否应该使用cookie 更多请参考 https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/withCredentials // (设置了withCredentials为true或者是同源请求)而且设置xsrfCookieName var xsrfValue = (config.withCredentials || isURLSameOrigin(config.url)) && config.xsrfCookieName ? // 读取cookie中XSRF-TOKEN cookies.read(config.xsrfCookieName) : undefined; if (xsrfValue) { // 在request header中设置XSRF-TOKEN requestHeaders[config.xsrfHeaderName] = xsrfValue; } } // setRequestHeader是用来设置请求头部的方法 if ('setRequestHeader' in request) { // 将config中配置的requestHeaders,循环设置到请求头上 utils.forEach(requestHeaders, function setRequestHeader(val, key) { if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') { delete requestHeaders[key]; } else { request.setRequestHeader(key, val); } }); } // 设置xhr对象的withCredentials属性,是否容许cookie进行跨域请求 if (config.withCredentials) { request.withCredentials = true; } // 设置xhr对象的responseType属性 if (config.responseType) { try { request.responseType = config.responseType; } catch (e) { if (config.responseType !== 'json') { throw e; } } } // 下载进度 if (typeof config.onDownloadProgress === 'function') { request.addEventListener('progress', config.onDownloadProgress); } // 上传进度 // request.upload XMLHttpRequest.upload 属性返回一个 XMLHttpRequestUpload对象,用来表示上传的进度 if (typeof config.onUploadProgress === 'function' && request.upload) { request.upload.addEventListener('progress', config.onUploadProgress); } if (config.cancelToken) { // 取消请求,在介绍/lib/cancel/CancelToken.js中以及介绍,这里不在赘述 config.cancelToken.promise.then(function onCanceled(cancel) { if (!request) { return; } request.abort(); reject(cancel); request = null; }); } if (requestData === undefined) { requestData = null; } // 发送http请求 request.send(requestData); }); };
dispatchRequest.js文件是axios源码中实际调用请求的地方。
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); // 若是包含baseUrl, 而且不是config.url绝对路径,组合baseUrl以及config.url if (config.baseURL && !isAbsoluteURL(config.url)) { // 组合baseURL与url造成完整的请求路径 config.url = combineURLs(config.baseURL, config.url); } config.headers = config.headers || {}; // 使用/lib/defaults.js中的transformRequest方法,对config.headers和config.data进行格式化 // 好比将headers中的Accept,Content-Type统一处理成大写 // 好比若是请求正文是一个Object会格式化为JSON字符串,并添加application/json;charset=utf-8的Content-Type // 等一系列操做 config.data = transformData( config.data, config.headers, config.transformRequest ); // 合并不一样配置的headers,config.headers的配置优先级更高 config.headers = utils.merge( config.headers.common || {}, config.headers[config.method] || {}, config.headers || {} ); // 删除headers中的method属性 utils.forEach( ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'], function cleanHeaderConfig(method) { delete config.headers[method]; } ); // 若是config配置了adapter,使用config中配置adapter的替代默认的请求方法 var adapter = config.adapter || defaults.adapter; // 使用adapter方法发起请求(adapter根据浏览器环境或者Node环境会有不一样) return adapter(config).then( // 请求正确返回的回调 function onAdapterResolution(response) { // 判断是否以及取消了请求,若是取消了请求抛出以取消 throwIfCancellationRequested(config); // 使用/lib/defaults.js中的transformResponse方法,对服务器返回的数据进行格式化 // 例如,使用JSON.parse对响应正文进行解析 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); } ); };
InterceptorManager.js文件中定义了axios拦截器类。包含了拦截器的添加,删除,循环拦截器。不管是响应拦截器仍是请求拦截器,都是使用数组进行存储的。
var utils = require('./../utils'); // 拦截器类 function InterceptorManager() { // handlers数组用来存储拦截器 this.handlers = []; } // 添加拦截器,use方法接收两个参数,成功的回调以及失败的回调 InterceptorManager.prototype.use = function use(fulfilled, rejected) { this.handlers.push({ // 成功的回调 fulfilled: fulfilled, // 失败的回调 rejected: rejected }); return this.handlers.length - 1; }; // 根据id(索引),删除实例handlers属性中拦截器 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); } }); }; module.exports = InterceptorManager;
Axios.js文件中定义了Axios实例上的request,get,post,delete方法。get,post,delete等方法均是基于Axios.prototype.request的封装📦。在Axios.prototype.request中会依次执行请求拦截器,dispatchRequest(实际发起),响应拦截器。总体的流程如👆上图所示。
var utils = require('./../utils'); var buildURL = require('../helpers/buildURL'); var InterceptorManager = require('./InterceptorManager'); var dispatchRequest = require('./dispatchRequest'); var mergeConfig = require('./mergeConfig'); function Axios(instanceConfig) { // Axios的配置 this.defaults = instanceConfig; // 拦截器 this.interceptors = { request: new InterceptorManager(), // 请求拦截器 response: new InterceptorManager() // 响应拦截器 }; } Axios.prototype.request = function request(config) { // 若是config是一个字符串,把字符串看成请求的url地址 if (typeof config === 'string') { config = arguments[1] || {}; config.url = arguments[0]; } else { config = config || {}; } // 合并配置 config = mergeConfig(this.defaults, config); // 若是没有指定请求方法,使用get方法 config.method = config.method ? config.method.toLowerCase() : 'get'; var promise = Promise.resolve(config); // 将请求拦截器,和响应拦截器,以及实际的请求(dispatchRequest)的方法组合成数组,相似以下的结构 // [请求拦截器1success, 请求拦截器1error, 请求拦截器2success, 请求拦截器2error, dispatchRequest, undefined, 响应拦截器1success, 响应拦截器1error] 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); }); // 开始执行整个请求流程(请求拦截器->dispatchRequest->响应拦截器) // 流程能够理解为上图⬆️ while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); } return promise; }; Axios.prototype.getUri = function getUri(config) { config = mergeConfig(this.defaults, config); return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, ''); }; // 基于Axios.prototype.request封装其余方法 // 将delete,get,head,options,post,put,patch添加到Axios.prototype的原型链上 // Axios.prototype.delete = // Axios.prototype.get = // Axios.prototype.head = // Axios.prototype.options = // ... 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 })); }; }); module.exports = Axios;
defaults.js文件中配置了,axios默认的请求头、不一样的环境下axios默认使用的请求方法、格式化请求正文的方法,格式化响应正文方法等内容
var utils = require('./utils'); var normalizeHeaderName = require('./helpers/normalizeHeaderName'); // 默认Content-Type var DEFAULT_CONTENT_TYPE = { 'Content-Type': 'application/x-www-form-urlencoded' }; // 设置ContentType,在没有设置的状况下 function setContentTypeIfUnset(headers, value) { if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) { headers['Content-Type'] = value; } } // 根据当前环境,获取默认的请求方法 function getDefaultAdapter() { var adapter; // 判断当前环境是否存在process对象 if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') { // node环境 adapter = require('./adapters/http'); } else if (typeof XMLHttpRequest !== 'undefined') { // 浏览器环境 adapter = require('./adapters/xhr'); } return adapter; } var defaults = { // 默认的请求方法 adapter: getDefaultAdapter(), // 格式化请求requestData,这会请求发送前使用 transformRequest: [ function transformRequest(data, headers) { // 格式化header属性名,将header中不标准的属性名,格式化为Accept属性名 normalizeHeaderName(headers, 'Accept'); // 格式化header属性名,将header中不标准的属性名,格式化为Content-Type属性名 normalizeHeaderName(headers, 'Content-Type'); if (utils.isFormData(data) || utils.isArrayBuffer(data) || utils.isBuffer(data) || utils.isStream(data) || utils.isFile(data) || utils.isBlob(data) ) { return data; } if (utils.isArrayBufferView(data)) { return data.buffer; } // URLSearchParams提供了一些用来处理URL查询字符串接口 // 若是是URLSearchParams对象 if (utils.isURLSearchParams(data)) { // Content-Type设置为application/x-www-form-urlencoded // application/x-www-form-urlencoded,数据被编码成以&分隔的键值对 setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8'); return data.toString(); } // 若是是对象 if (utils.isObject(data)) { // Content-Type设置为application/json setContentTypeIfUnset(headers, 'application/json;charset=utf-8'); // 将请求正文格式化为JSON字符串,并返回 return JSON.stringify(data); } return data; } ], // 格式化响应resposeData,这会响应接受后使用 transformResponse: [ function transformResponse(data) { if (typeof data === 'string') { try { data = JSON.parse(data); } catch (e) { /* Ignore */ } } return data; } ], // 默认超时时间 timeout: 0, // xsrf设置的cookie的key xsrfCookieName: 'XSRF-TOKEN', // xsrf设置header的key xsrfHeaderName: 'X-XSRF-TOKEN', maxContentLength: -1, // 验证请求的状态 // 在处理请求的Promise会被使用 validateStatus: function validateStatus(status) { return status >= 200 && status < 300; } }; defaults.headers = { // 通用的HTTP字段 // Accept告知客户端能够处理的类型 common: { 'Accept': 'application/json, text/plain, */*' } }; utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) { defaults.headers[method] = {}; }); // 为post,put,patch请求设置默认的Content-Type utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE); }); module.exports = defaults;
axios.js文件是axios工具库的入口方法,在axios.js
var utils = require('./utils'); var bind = require('./helpers/bind'); var Axios = require('./core/Axios'); var mergeConfig = require('./core/mergeConfig'); var defaults = require('./defaults'); // 建立axios实例 function createInstance(defaultConfig) { var context = new Axios(defaultConfig); // 更改Axios.prototype.request的this,执行context实例 // instance等于Axios.prototype.request方法 var instance = bind(Axios.prototype.request, context); // 将Axios.prototype,context上的属性合并到instance // instance.get = Axios.prototype.get // instance.defaults = context.defaults // ... utils.extend(instance, Axios.prototype, context); utils.extend(instance, context); return instance; } // axios会直接对使用者暴露一个axios.request的方法,因此咱们在使用axios的时候能够这样使用。不须要new一个axios的实例 // import axios from 'axios' // axios.get('/info') var axios = createInstance(defaults); axios.Axios = Axios; // axios.create能够根据用户自定义的config生成一个新的axios实例 axios.create = function create(instanceConfig) { return createInstance(mergeConfig(axios.defaults, instanceConfig)); }; axios.Cancel = require('./cancel/Cancel'); axios.CancelToken = require('./cancel/CancelToken'); axios.isCancel = require('./cancel/isCancel'); axios.all = function all(promises) { return Promise.all(promises); }; axios.spread = require('./helpers/spread'); module.exports = axios; module.exports.default = axios;