相信做前端的同学们,应该没有人没听说过axios的鼎鼎大名吧!用了这么久的axios,是否也会好奇axios是如何实现的呢?今天就让我们走进axios的源码,学习这款神级工具是如何实现的吧!
lib │ axios.js // 最终导出的文件 │ utils.js // 工具类 ├─adapters // 适配器相关 │ adapters.js //适配器类 │ http.js // node请求 │ xhr.js // 浏览器请求 ├─cancel // 取消功能相关 │ CanceledError.js //取消异常类 │ CancelToken.js //取消token类 │ isCancel.js //判断是否取消 ├─core // 核心功能相关 │ Axios.js // axios类 │ AxiosError.js // axios异常类 │ AxiosHeaders.js // 请求头 │ buildFullPath.js // 构造请求地址 │ dispatchRequest.js // 发送请求方法 │ InterceptorManager.js // 拦截器的类 │ mergeConfig.js // 合并配置方法 │ settle.js // 处理请求结果方法 │ transformData.js // 数据转换执行方法 ├─defaults // 默认配置 │ index.js // 默认请求参数配置 │ transitional.js // 默认transitional配置 ├─env // node环境没有FormData, │ │ data.js │ └─classes │ FormData.js ├─helpers // 各种工具类方法,看名字就可以大概猜到作用 │ AxiosTransformStream.js │ AxiosURLSearchParams.js │ bind.js │ buildURL.js │ callbackify.js │ combineURLs.js │ cookies.js │ deprecatedMethod.js │ formDataToJSON.js │ formDataToStream.js │ fromDataURI.js │ HttpStatusCode.js │ isAbsoluteURL.js │ isAxiosError.js │ isURLSameOrigin.js │ null.js │ parseHeaders.js │ parseProtocol.js │ readBlob.js │ README.md │ speedometer.js │ spread.js │ throttle.js │ toFormData.js │ toURLEncodedForm.js │ validator.js │ ZlibHeaderTransformStream.js └─platform // 为不同环境下准备的方法 │ index.js ├─browser │ │ index.js │ └─classes │ Blob.js │ FormData.js │ URLSearchParams.js └─node │ index.js └─classes FormData.js URLSearchParams.js
// 核心方法,根据config创建axios实例 function createInstance (defaultConfig) { // 创建axios实例 const context = new Axios(defaultConfig); // 给Axios原型上的request方法绑定context为它的this // 这个instance就是我们最终使用的axios // 没想到吧,最开始的instance其实是个函数, // 所以我们才可以使用这个用法axios('/api/url') // 只不过后面给它扩展了很多东西 const instance = bind(Axios.prototype.request, context); // 将Axios.prototype上的属性都绑定到instance上, // 这样它就拥有了简写的请求方法,比如axios.get(),axios.post() // 如果是函数,this绑定为context utils.extend(instance, Axios.prototype, context, { allOwnKeys: true }); // 将context上的属性都绑定到instance上, // 这样它就拥有了拦截器属性,可以使用axios.interceptors.request.use() // 因为context上的函数的this本就指向context,所以第三个参数不需要再指定 utils.extend(instance, context, null, { allOwnKeys: true }); // 给instance增加create方法,可以通过create创建一个实例 instance.create = function create (instanceConfig) { // 入参为拼接配置项,以instanceConfig为优先 return createInstance(mergeConfig(defaultConfig, instanceConfig)); }; return instance; } // 调用上面的方法,最终导出的是axios, // 其实是Axios.prototype.request,并扩展了很多属性 const axios = createInstance(defaults); // 继续给axios增加属性 // 这就说明如果自己通过const myAxios=axios.create({}); // 创建出来的实例就没有下面这些属性了 // 所以下面这些属性只能通过import axios from 'axios'; // axios.all()这样的方式来使用 axios.Axios = Axios; // Cancel相关 axios.CanceledError = CanceledError; axios.CancelToken = CancelToken; axios.isCancel = isCancel; axios.VERSION = VERSION; // 工具函数,将对象转为FormData axios.toFormData = toFormData; // Axios通用异常类 axios.AxiosError = AxiosError; // Cancel异常类 axios.Cancel = axios.CanceledError; // Expose all/spread // 工具函数 axios.all = function all (promises) { return Promise.all(promises); }; // 工具函数,利用apply将数组参数改为单个传入的参数 axios.spread = spread; // 判断异常是否是AxiosError axios.isAxiosError = isAxiosError; // 合并config对象方法 axios.mergeConfig = mergeConfig; axios.AxiosHeaders = AxiosHeaders; // 工具方法 axios.formToJSON = thing => formDataToJSON(utils.isHTMLForm(thing) ? new FormData(thing) : thing); // 获取适配器:xhr 、http axios.getAdapter = adapters.getAdapter; // 请求状态 axios.HttpStatusCode = HttpStatusCode; axios.default = axios; // 最终导出 export default axios4.2 Axios类
class Axios { // 可以看到Axios的构造函数相当简单 // 仅仅是保存了我们传入的config, // 然后初始化空的拦截器对象 constructor(instanceConfig) { // 所有的配置都设置再defaults上 this.defaults = instanceConfig; // 初始化空的拦截器对象,包含请求拦截器request和返回拦截器response this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager() }; } // request是Axios的核心方法 // 所有的核心都在request方法里, // request方法接收两种参数,【直接传config对象】或者【传url和config对象】 request (configOrUrl, config) { // 允许axios('example/url'[, config]) 这样使用 if (typeof configOrUrl === 'string') { config = config || {}; config.url = configOrUrl; } else { config = configOrUrl || {}; } // request会使用传入的配置merge默认配置 // 所以即使只传了一个url,也会使用默认的Get方法 config = mergeConfig(this.defaults, config); const { headers } = config; // 默认get请求 config.method = (config.method || this.defaults.method || 'get').toLowerCase(); // 说明header可以直接设置 // 也可以在common设置通用header,也可以为每种请求设置特定的header let contextHeaders = headers && utils.merge( headers.common, headers[config.method] ); headers && utils.forEach( ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'], (method) => { delete headers[method]; } ); // 优先使用headers下配置,再使用headers.common和headers[get,post]的配置 config.headers = AxiosHeaders.concat(contextHeaders, headers); // 请求拦截器链 const requestInterceptorChain = []; // 记录是否使用同步的方式调用,我们配置拦截器的时候,默认是false,也就是异步 let synchronousRequestInterceptors = true; this.interceptors.request.forEach(function unshiftRequestInterceptors (interceptor) { // 如果配置了runWhen函数,那么会先执行runWhen,如果为true,才会添加该拦截器 if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) { return; } synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous; // unshift说明后传入的请求拦截器先执行,一次放入两个,分别是fulfilled和rejected requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected); }); // 响应拦截器链 const responseInterceptorChain = []; this.interceptors.response.forEach(function pushResponseInterceptors (interceptor) { // push说明先传入的响应拦截器先执行 responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected); }); let promise; let i = 0; let len; // 默认是异步执行,也就是一个执行完再执行下一个 if (!synchronousRequestInterceptors) { //dispatchRequest是真正的发送请求 const chain = [dispatchRequest.bind(this), undefined]; // 前面插入请求拦截器 chain.unshift.apply(chain, requestInterceptorChain); // 后面插入响应拦截器 chain.push.apply(chain, responseInterceptorChain); len = chain.length; promise = Promise.resolve(config); // 依次执行 while (i < len) { promise = promise.then(chain[i++], chain[i++]); } return promise; } len = requestInterceptorChain.length; let newConfig = config; i = 0; // 同步执行,请求拦截器 while (i < len) { const onFulfilled = requestInterceptorChain[i++]; const onRejected = requestInterceptorChain[i++]; try { newConfig = onFulfilled(newConfig); } catch (error) { onRejected.call(this, error); break; } } // 发起请求 try { promise = dispatchRequest.call(this, newConfig); } catch (error) { return Promise.reject(error); } i = 0; len = responseInterceptorChain.length; // 返回有异常可以继续走下去 while (i < len) { promise = promise.then(responseInterceptorChain[i++], responseInterceptorChain[i++]); } return promise; } // 获取请求地址 getUri (config) { config = mergeConfig(this.defaults, config); const fullPath = buildFullPath(config.baseURL, config.url); return buildURL(fullPath, config.params, config.paramsSerializer); } } // Provide aliases for supported request methods // 给Axios原型注入四个请求方法,请求方法本质都是调用request方法 // 这四个都是不带请求体的 utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData (method) { Axios.prototype[method] = function (url, config) { return this.request(mergeConfig(config || {}, { method, url, data: (config || {}).data })); }; }); // 给Axios注入post,put,patch,postForm,putForm,patchForm方法 // 这几个方法都是带请求体的 utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData (method) { function generateHTTPMethod (isForm) { return function httpMethod (url, data, config) { return this.request(mergeConfig(config || {}, { method, headers: isForm ? { 'Content-Type': 'multipart/form-data' } : {}, url, data })); }; } Axios.prototype[method] = generateHTTPMethod(); Axios.prototype[method + 'Form'] = generateHTTPMethod(true); }); export default Axios;4.3 InterceptorManager类
axios.interceptors.request.use({ fulfilled:()=>{}, rejected:()=>{} })可以看到我们给use传递的是一个对象,对象包含fulfilled函数和rejected函数。
class InterceptorManager { // 构造函数只初始化了一个空的handlers数组 // 拦截器就是放在这个数组里的 constructor() { this.handlers = []; } // 添加拦截器,返回索引,可以用索引来移除拦截器 // 可以发现除了fulfilled和rejected, // 我们还可以设置synchronous和runWhen // runWhen函数用来动态控制是否使用该拦截器 use (fulfilled, rejected, options) { this.handlers.push({ fulfilled, rejected, synchronous: options ? options.synchronous : false, runWhen: options ? options.runWhen : null }); return this.handlers.length - 1; } // 根据添加时返回的索引去删除拦截器 eject (id) { if (this.handlers[id]) { this.handlers[id] = null; } } // 清空拦截器 clear () { if (this.handlers) { this.handlers = []; } } // 堆代码 duidaima.com // 提供遍历拦截器快捷操作 forEach (fn) { utils.forEach(this.handlers, function forEachHandler (h) { if (h !== null) { fn(h); } }); } } export default InterceptorManager;4.4 dispatchRequest发送请求
// 暂且先记住,这个函数的作用就是用来判断请求是否被取消, // 如果要的话,则直接抛出异常, function throwIfCancellationRequested (config) { if (config.cancelToken) { config.cancelToken.throwIfRequested(); } if (config.signal && config.signal.aborted) { throw new CanceledError(null, config); } } // 发送请求核心函数 export default function dispatchRequest (config) { // 刚开始请求前判断一次是否取消 throwIfCancellationRequested(config); config.headers = AxiosHeaders.from(config.headers); // 执行数据转换操作 config.data = transformData.call( config, config.transformRequest ); // 默认设置请求头的contentType为application/x-www-form-urlencoded if (['post', 'put', 'patch'].indexOf(config.method) !== -1) { config.headers.setContentType('application/x-www-form-urlencoded', false); } // 获取适配器,如果是浏览器环境获取xhr, // 如果是Node环境,获取http // 适配器就是最终用来发送请求的东西 const adapter = adapters.getAdapter(config.adapter || defaults.adapter); // 请求是使用适配器执行config return adapter(config).then(function onAdapterResolution (response) { // 请求完之后判断是否要取消 throwIfCancellationRequested(config); // 对返回结果进行转换 response.data = transformData.call( config, config.transformResponse, response ); // 设置返回头 response.headers = AxiosHeaders.from(response.headers); return response; }, function onAdapterRejection (reason) { // 如果不是因为取消而报错 if (!isCancel(reason)) { // 再次判断是否要取消,如果是会抛出异常 throwIfCancellationRequested(config); // 处理正常错误的返回值 if (reason && reason.response) { reason.response.data = transformData.call( config, config.transformResponse, reason.response ); reason.response.headers = AxiosHeaders.from(reason.response.headers); } } return Promise.reject(reason); }); }4.5 adapter 请求适配器,此处以xhr请求适配器为例
// 用于给上传和下载进度增加监听函数 function progressEventReducer (listener, isDownloadStream) { let bytesNotified = 0; const _speedometer = speedometer(50, 250); return e => { const loaded = e.loaded; const total = e.lengthComputable ? e.total : undefined; const progressBytes = loaded - bytesNotified; const rate = _speedometer(progressBytes); const inRange = loaded <= total; bytesNotified = loaded; const data = { loaded, total, progress: total ? (loaded / total) : undefined, bytes: progressBytes, rate: rate ? rate : undefined, estimated: rate && total && inRange ? (total - loaded) / rate : undefined, event: e }; data[isDownloadStream ? 'download' : 'upload'] = true; listener(data); }; } // 判断是否支持XMLHttpRequest const isXHRAdapterSupported = typeof XMLHttpRequest !== 'undefined'; // 适配器的请求参数是config export default isXHRAdapterSupported && function (config) { // 返回Promise return new Promise(function dispatchXhrRequest (resolve, reject) { // 请求体 let requestData = config.data; // 请求头 const requestHeaders = AxiosHeaders.from(config.headers).normalize(); // 返回数据类型 const responseType = config.responseType; let onCanceled; // function done () { if (config.cancelToken) { config.cancelToken.unsubscribe(onCanceled); } if (config.signal) { config.signal.removeEventListener('abort', onCanceled); } } // 自动帮我们设置contentType, // 这就是为什么我们使用的时候都不需要 // 特别设置contentType的原因了 if (utils.isFormData(requestData)) { if (platform.isStandardBrowserEnv || platform.isStandardBrowserWebWorkerEnv) { // 浏览器环境让浏览器设置 requestHeaders.setContentType(false); } else { requestHeaders.setContentType('multipart/form-data;', false); } } // 请求 let request = new XMLHttpRequest(); // 设置auth,帮我们转码好了 if (config.auth) { const username = config.auth.username || ''; const password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : ''; requestHeaders.set('Authorization', 'Basic ' + btoa(username + ':' + password)); } // 拼接完整URL路径 const fullPath = buildFullPath(config.baseURL, config.url); // 开启请求 request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true); // 设置超时时间 request.timeout = config.timeout; // function onloadend () { if (!request) { return; } // 预准备返回体的内容 const responseHeaders = AxiosHeaders.from( 'getAllResponseHeaders' in request && request.getAllResponseHeaders() ); const responseData = !responseType || responseType === 'text' || responseType === 'json' ? request.responseText : request.response; const response = { data: responseData, status: request.status, statusText: request.statusText, headers: responseHeaders, config, request }; // 请求完之后判断请求是成功还是失败 // 执行resolve和reject的操作 settle(function _resolve (value) { resolve(value); done(); }, function _reject (err) { reject(err); done(); }, response); // 清除request request = null; } if ('onloadend' in request) { // 设置onloadend request.onloadend = onloadend; } else { // Listen for ready state to emulate onloadend request.onreadystatechange = function handleLoad () { if (!request || request.readyState !== 4) { 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)) { return; } // readystate handler is calling before onerror or ontimeout handlers, // so we should call onloadend on the next 'tick' // readystate之后再执行onloadend setTimeout(onloadend); }; } // 处理浏览器请求取消事件 request.onabort = function handleAbort () { if (!request) { return; } reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request)); request = null; }; // 处理低级的网络错误 request.onerror = function handleError () { reject(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request)); request = null; }; // 处理超时 request.ontimeout = function handleTimeout () { let timeoutErrorMessage = config.timeout ? 'timeout of ' + config.timeout + 'ms exceeded' : 'timeout exceeded'; const transitional = config.transitional || transitionalDefaults; if (config.timeoutErrorMessage) { timeoutErrorMessage = config.timeoutErrorMessage; } reject(new AxiosError( timeoutErrorMessage, transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED, config, request)); request = null; }; // 添加 xsrf if (platform.isStandardBrowserEnv) { const xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName && cookies.read(config.xsrfCookieName); if (xsrfValue) { requestHeaders.set(config.xsrfHeaderName, xsrfValue); } } // 无请求体的话就移除contentType requestData === undefined && requestHeaders.setContentType(null); // 添加headers if ('setRequestHeader' in request) { utils.forEach(requestHeaders.toJSON(), function setRequestHeader (val, key) { request.setRequestHeader(key, val); }); } // 添加withCredentials if (!utils.isUndefined(config.withCredentials)) { request.withCredentials = !!config.withCredentials; } // 添加responseType if (responseType && responseType !== 'json') { request.responseType = config.responseType; } // 增加下载过程的监听函数 if (typeof config.onDownloadProgress === 'function') { request.addEventListener('progress', progressEventReducer(config.onDownloadProgress, true)); } // 增加上传过程的监听函数 if (typeof config.onUploadProgress === 'function' && request.upload) { request.upload.addEventListener('progress', progressEventReducer(config.onUploadProgress)); } // 请求过程中取消 if (config.cancelToken || config.signal) { onCanceled = cancel => { if (!request) { return; } reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel); request.abort(); request = null; }; config.cancelToken && config.cancelToken.subscribe(onCanceled); if (config.signal) { config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled); } } // 获取请求协议,比如https这样的 const protocol = parseProtocol(fullPath); // 判断当前环境是否支持该协议 if (protocol && platform.protocols.indexOf(protocol) === -1) { reject(new AxiosError('Unsupported protocol ' + protocol + ':', AxiosError.ERR_BAD_REQUEST, config)); return; } // 发送请求 request.send(requestData || null); }); }五. 结束