axios源码分析
- axios的入口是在 lib/ axios.js
function createInstance(defaultConfig) { var context = new Axios(defaultConfig); var instance = bind(Axios.prototype.request, context); // Copy axios.prototype to instance utils.extend(instance, Axios.prototype, context); // Copy context to instance utils.extend(instance, context); return instance;}// Create the default instance to be exportedvar axios = createInstance(defaults);// Expose Axios class to allow class inheritanceaxios.Axios = Axios;// Factory for creating new instancesaxios.create = function create(instanceConfig) { return createInstance(utils.merge(defaults, instanceConfig));}; - 当我们在调用axios.create({})方法的时候,会去调用createInstance方法,这个方法中接收一个合并配置的参数,(在axios中会有一些默认的配置,像headers,请求参数的序列化,timeout,还有最重要的就是XMLHttpRequest实例方法)最后这个方法return 一个instance。这个instance就是函数中调用Axios的request方法,最后返回的一个实例。 这个函数最终调用的就是Axios.request
- 让我们找到Axios.request的方法 core/Axios.js
Axios.prototype.request = function request(config) { // Allow for axios('example/url'[, config]) a la fetch API if (typeof config === 'string') { config = utils.merge({ url: arguments[0] }, arguments[1]); } config = utils.merge(defaults, this.defaults, { method: 'get' }, config); config.method = config.method.toLowerCase(); // Support baseURL config if (config.baseURL && !isAbsoluteURL(config.url)) { config.url = combineURLs(config.baseURL, config.url); } // Hook up interceptors middleware var chain = [dispatchRequest, undefined]; var promise = Promise.resolve(config); 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); }); while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); } return promise;}; - 这个方法中的参数config就是上面合并了所有配置的config。如果config是个字符串那么会把URL单独拿出来 和其他参数做一次合并。关键逻辑:var chain = [dispatchRequest, undefined]; var promise = Promise.resolve(config); while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); }return promise; 这里就是最终去执行了dispatchRequest(config)这个方法。
- 来看下dispatchRequest core/dispatchRequest.js
module.exports = function dispatchRequest(config) { throwIfCancellationRequested(config); // Ensure headers exist config.headers = config.headers || {}; // Transform request data config.data = transformData( config.data, config.headers, config.transformRequest ); // Flatten headers 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;//这里的adapter 是从defaults.js中合并过来的 return adapter(config).then(function onAdapterResolution(response) { throwIfCancellationRequested(config); // Transform response data response.data = transformData( response.data, response.headers, config.transformResponse ); return response; }, function onAdapterRejection(reason) { if (!isCancel(reason)) { throwIfCancellationRequested(config); // Transform response data if (reason && reason.response) { reason.response.data = transformData( reason.response.data, reason.response.headers, config.transformResponse ); } } return Promise.reject(reason); });}; - 这个方法一进来的是也是一样 合并处理。关于合并处理 其实有很多东西可以讲 这里为了节省时间 不再细讲。感兴趣的朋友可以翻看源码看一下。那么这个方法中最关键的就是var adapter = config.adapter || defaults.adapter; return adapter(config)。 这个adapter其实就是从defaults中拿过来的,它是我们能够实现Ajax请求 并使用Promise链式调用的关键逻辑。
- adapter lib/defaults.js
function getDefaultAdapter() { var adapter; if (typeof XMLHttpRequest !== 'undefined') { // For browsers use XHR adapter adapter = require('./adapters/xhr'); } else if (typeof process !== 'undefined') { // For node use HTTP adapter adapter = require('./adapters/http'); } return adapter;} var defaults = { adapter: getDefaultAdapter(), transformRequest: [function transformRequest(data, headers) { 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; } if (utils.isURLSearchParams(data)) { setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8'); return data.toString(); } if (utils.isObject(data)) { setContentTypeIfUnset(headers, 'application/json;charset=utf-8'); return JSON.stringify(data); } return data; }], transformResponse: [function transformResponse(data) { /*eslint no-param-reassign:0*/ if (typeof data === 'string') { try { data = JSON.parse(data); } catch (e) { /* Ignore */ } } return data; }], timeout: 0, xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', maxContentLength: -1, validateStatus: function validateStatus(status) { return status >= 200 && status < 300; }}; - 上面我们只需要关注 adapter: getDefaultAdapter(),即可。 getDefaultAdapter 这个方法 adapter = require('./adapters/xhr'); 最终从xhr这个js中拿到了一个 new promise() 里面写了XMLHttpRequest 实例的方法和配置。 到这也就明白了axios是怎么实现promise链式调用的。
知道了原理以后我们可以试着自己写一个。
/* index.js */
import instance from "./defaults";function Ask(options) { return instance(options);}export default Ask;
/**defaults.js */
import { buildUrl } from "./utils.js";export default function(options) { return new Promise((reslove, reject) => { let requestData = options.data; let requestHeaders = options.headers; let xhr = new XMLHttpRequest(); xhr.open( options.method.toUpperCase(), buildUrl(options.url, options.params) ); xhr.timeout = options.timeout; xhr.onreadystatechange = function statechange() { if (xhr.readyState !== 4) { return; } let responseData = { data: JSON.parse(xhr.response), status: xhr.status }; console.log(responseData, 'responseData') if ( !xhr.status || (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 ) { reslove(responseData); } else { reject("request error"); } }; xhr.ontimeout = function timeout() { reject("error time out"); }; if ("setRequestHeader" in xhr) { for (let k in requestHeaders) { if (requestHeaders.hasOwnProperty(k)) { let v = requestHeaders[k]; xhr.setRequestHeader(k, v); } } } if (requestData === undefined) { requestData = null; } xhr.send(requestData); });}
/** utils.js*/
export const buildUrl = (url, params) => { if (!params) { return url; } let parts = []; let serializedParams; for (let key in params) { if (params.hasOwnProperty(key)) { let str = encodeURIComponent(key) + "=" + encodeURIComponent(params[key]); parts.push(str); } } if (parts.length) { serializedParams = parts.join("&"); url += (url.indexOf("?") === -1 ? "?" : "&") + serializedParams; return url; }};- 目前只是实现了一些基本的功能。像axios.all axios.get axios.post 等等这些功能还有待完善 但是我们只要理解了原理以后 再去写功能 就是灰常轻松愉快的事情了。
欢迎大佬多多指教 不足之处 还望轻喷!