从入口开始
查看 axios 源码,找到 package.json main
属性值 index.js
,也就是说入口文件是根目录下的 index.js
。
分析入口文件可以看到,引入了 ./lib/axios
模块,接着导出成员 axios 作为默认导出成员:
import axios from './lib/axios.js';
export {
axios as default,
...
}
也就是说,我们平时引入使用的 import axios from 'axios'
就是 ./lib/axios
模块里导出的成员。
所以,分析 axios 核心源码,我们便可以从 ./lib/axios
入手。
axios 实例
大概过一遍该文件,不用细究细节,可以了解到一个大概的代码情况,一般我会先看最后导出的成员,然后接着往上看。
该模块导出了一个默认成员 axios
export default axios;
往上找,axios 是什么呢?分析代码发现其实是一个创建的实例,然后在该实例上绑定了很多的属性、方法
// Create the default instance to be exported
const axios = createInstance(defaults);
// Expose Axios class to allow class inheritance
axios.Axios = Axios;
// Expose all/spread
axios.all = function all(promises) {
return Promise.all(promises);
};
axios.default = axios; // 这个应该在项目中经常看到,做一些默认配置用
...
// this module should only have a default export
export default axios
顺着找, createInstance
这里,这个函数是专门用来新建一个 axios 实例的。
createInstance
// ./lib/axios.js
function createInstance(defaultConfig) {
//
const context = new Axios(defaultConfig);
const instance = bind(Axios.prototype.request, context);
// Copy axios.prototype to instance
utils.extend(instance, Axios.prototype, context, {allOwnKeys: true});
// Copy context to instance
utils.extend(instance, context, null, {allOwnKeys: true});
// Factory for creating new instances
instance.create = function create(instanceConfig) {
return createInstance(mergeConfig(defaultConfig, instanceConfig));
};
return instance;
}
// ./lib/helpers/bind.js
export default function bind(fn, thisArg) {
return function wrap() {
return fn.apply(thisArg, arguments);
};
}
在这里做了几件事:
- 基于 Axios 类新建了一个 context 实例
- 把 context 绑定给 Axios 原型的 request 方法,这样 request 调用的时候,里面的 this 才会指向这个内部新建的 context 实例,这样即使是创建多个 axios 实例,它们互相之间是隔离不干扰的
- bind 函数在这里的作用就是修改
Axios.prototype.request
的this
指向。instance
可以理解为Axios.prototype.request
,这里虽然有点绕,但还是很好理解,instance
是一个wrap
包装函数,只要instance
被调用,也就是里面的包装函数wrap()
执行,也就是Axios.prototype.request
被立即调用 - 换句话说,
instance
指的是Axios.prototype.request
(不正确但好理解)
import axios from 'axios'
// 这里我们平时是这样调用 axios 的,
// 而实际上,是调用了内部的 Axios.prototype.request 方法
axios({
url: '/api',
method: 'get'
})
// 平时我们使用的创建 axios 实例的用法:
// 其实是因为源码内部 instance.create 方法里,内部调用的也是 createInstance
// 也就是说,官方导出的默认 axios 实例,和我们自己使用 axios.create 新创建的 axiosInstance 本质上是一样的创建逻辑
const axiosInstance = axios.create({})
- 把
Axios
的原型拷贝到instance
,为什么?下方解释 - 把
context
拷贝到instance
,为什么?下方解释
Axios 类
把 Axios
的原型拷贝到 instance
作用是让
instance
共享 Axios 原型对象上的属性、方法,比如使用 axios.get、axios.post 等 API 发送请求
原因是 我们在使用 axios.get(url)
、axios.post(url)
等请求时,在 instance 上面是没有绑定这些方法的,所以需要手动绑定这些方法到 instance。
在源码中,这部分代码是在 Axios.js 模块实现的:
'use strict';
import utils from './../utils.js';
const validators = validator.validators;
class Axios {
constructor(instanceConfig) {
}
async request(configOrUrl, config) {
}
}
// Provide aliases for supported request methods
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, config) {
return this.request(mergeConfig(config || {}, {
method,
url,
data: (config || {}).data
}));
};
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
/*eslint func-names:0*/
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;
把 context
拷贝到 instance
作用是让
instance
共享创建的Axios
实例即context
的属性、方法,比如 inteceptors、request方法
根据 axios 的官方 API,我们可以看到这些常见的 API:
import axios from 'axios'
axios.request(url)
axios.interceptors.request.use()
axios.interceptors.response.use()
这些方法都是 Axios 类自身需要实现的,request 就是 Axios 实例 context 的方法,而 interceptors 拦截器的实现则是分离出去抽象实现了一个 InterceptorManager 类,封装了处理拦截器相关的操作。拦截器又分为请求拦截器、响应拦截器。
// ./lib/core/Axios.js
class Axios {
constructor(defaultConfig) {
this.defaults = defaultConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
}
}
async request(configOrUrl, config) {
await this._request(configOrUrl, config);
}
// 重要的实现在这里
_request(configOrUrl, config) {
// TODO
}
}
// ./lib/core/InterceptorManager.js
class InterceptorManager {
constructor() {
this.handlers = [];
}
use(fulfilled, rejected) {
this.handlers.push({
fulfilled,
rejected,
})
}
forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
}
}
拦截器的实现思路
_request(configOrUrl, config) {
// 处理配置信息
if (typeof configOrUrl === 'string') {
config = config || {};
config.url = configOrUrl;
} else {
config = configOrUrl || {};
}
// 合并配置
config = mergeConfig(this.defaults, config);
...
// 存放请求拦截器
const requestInterceptorChain = [];
let synchronousRequestInterceptors = true; // 判断是否同步执行拦截器(主要控制请求拦截器)
// forEach 是 axios 自身实现的,遍历拦截器
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {// interceptor 实则是对应到 InterceptorManager 的一个 handler
// 只有在所有的拦截器的 synchronus 为 true,才是同步(默认为false,即异步)
synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
// 后添加的先执行,fulfilled 和 rejected 成对添加
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// 存放响应拦截器
const responseInterceptorChain = [];
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
// 响应拦截器按书写的先后顺序执行,fulfilled 和 rejected 成对添加
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
let promise;
let i = 0;
let len;
// 异步执行请求拦截器
if (!synchronousRequestInterceptors) {
// dispatchRequest 内部封装了真正的发起请求功能
const chain = [dispatchRequest.bind(this), undefined]; // 默认的调用链,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) {
// 把请求拦截器中的 fulfilled、reject取出
const onFulfilled = requestInterceptorChain[i++];
const onRejected = requestInterceptorChain[i++];
try {
newConfig = onFulfilled(newConfig); // 执行
} catch (error) {
// 捕获到错误,执行错误回调,且 break 终止下一个请求拦截器
// 与异步的区别:同步模式的错误处理针对与当前执行函数
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;
}
从拦截器源码中,可以总结以下几点重要的点:
- 请求拦截器分为同步执行和异步执行
- 只有所有的请求拦截器配置了
synchronus=true
才是同步,不然异步 - 同步、异步的区别在于错误捕获的处理,异步捕获的错误是之前节点最近的一次错误;同步捕获的错误是当前请求拦截器的错误,www.jianshu.com/p/710636bd7…
- 在发出请求前,执行请求拦截器,需要在请求拦截器执行函数最后返回 config,且后添加的先执行;请求完成后,把 response 响应结果返回传给响应拦截器,执行响应拦截器函数,也需要把 response 传给下一个响应拦截器
发送 XHR 请求
在上面我们看到有一个 dispatchRequest
函数,这个方法就是用来发起请求的,内部的实现兼容了浏览器端的 xhr 和 node 端的 http。
// lib/core/dispatchRequest.js
export default function dispatchRequest(config) {
...
// 在这里调用了getAdapter
const adapter = adapters.getAdapter(config.adapter || defaults.adapter);
return adapter(config).then(function onAdapterResolution(response) {
...
return response;
}, function onAdapterRejection(reason) {
if (!isCancel(reason)) {
...
}
return Promise.reject(reason);
});
}
getAdapter
// ./lib/adapters/adapters.js
import httpAdapter from './http.js';
import xhrAdapter from './xhr.js';
const knownAdapters = {
http: httpAdapter, // 用于 node 端发送 http 请求
xhr: xhrAdapter // 用于浏览器端发送 XMLHttpRequest 请求
}
const isResolvedHandle = (adapter) => utils.isFunction(adapter) || adapter === null || adapter === false;
export default {
getAdapter: (adapters) => {
adapters = utils.isArray(adapters) ? adapters : [adapters];
const {length} = adapters;
let nameOrAdapter;
let adapter;
const rejectedReasons = {};
for (let i = 0; i < length; i++) {
nameOrAdapter = adapters[i];
let id;
adapter = nameOrAdapter;
if (!isResolvedHandle(nameOrAdapter)) {
// 在这里根据 knownAdapters 映射得出对应的 adapter
adapter = knownAdapters[(id = String(nameOrAdapter)).toLowerCase()];
}
}
return adapter;
},
adapters: knownAdapters
}
发送 xhr 请求
源码比较复杂,可以做个大概了解
// ./lib/adapters/xhr.js
const isXHRAdapterSupported = typeof XMLHttpRequest !== 'undefined';
export default isXHRAdapterSupported && function (config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
let request = new XMLHttpRequest();
const fullPath = buildFullPath(config.baseURL, config.url);
request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);
// Set the request timeout in MS
request.timeout = config.timeout;
function onloadend() {
if (!request) {
return;
}
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
};
settle(function _resolve(value) {
resolve(value);
done();
}, function _reject(err) {
reject(err);
done();
}, response);
// Clean up request
request = null;
}
if ('onloadend' in request) {
// Use onloadend if available
request.onloadend = onloadend;
} else {
// Listen for ready state to emulate onloadend
request.onreadystatechange = function handleLoad() {
if (!request || request.readyState !== 4) {
return;
}
if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
return;
}
setTimeout(onloadend);
};
}
request.onabort = function handleAbort() {
if (!request) {
return;
}
...
// Clean up request
request = null;
};
// Handle low level network errors
request.onerror = function handleError() {
...
// Clean up request
request = null;
};
// Handle timeout
request.ontimeout = function handleTimeout() {
...
// Clean up request
request = null;
};
// Add responseType to request if needed
if (responseType && responseType !== 'json') {
request.responseType = config.responseType;
}
// Send the request
request.send(requestData || null);
});
}
但是实现一个 XMLHttpRequest 请求必须的三个是 o o s
,open onload send
,以下是使用 Promise 实现 XHR 请求的简单例子:
export default isXHRAdapterSupported && function (config) {
return new Promise((resolve) => {
const { url, method, data = {} } = config;
const xhr = new XMLHttpRequest();
xhr.open(url, method, true);
xhr.onload = function() {
resolve(xhr.responseText);
}
xhr.send(data)
})
}
原文链接:https://juejin.cn/post/7334723606259908645 作者:jiayinkong