Axios源码笔记 | 深入剖析 Adapters 适配器源码,揭开请求处理的神秘面纱
一、引言
在前端开发中,Axios 是一个广泛使用的基于 Promise 的 HTTP 客户端,它可以在浏览器和 Node.js 环境中工作。Axios 的强大之处在于其模块化的设计,其中适配器(Adapters)模块起着关键作用,它允许 Axios 在不同的环境中使用不同的请求方式。本文将深入解析 axios-1.x/lib/adapters 目录下的主要文件,包括 adapters.js、fetch.js、http.js 和 xhr.js,带你了解其实现原理和设计思路。
二、源码阅读与详细解析
2.1 adapters.js
adapters.js 主要负责管理和获取不同的适配器。
2.1.1 关键代码
import utils from '../utils.js';
import httpAdapter from './http.js';
import xhrAdapter from './xhr.js';
import fetchAdapter from './fetch.js';
import AxiosError from "../core/AxiosError.js";
const knownAdapters = {
http: httpAdapter,
xhr: xhrAdapter,
fetch: fetchAdapter
}
utils.forEach(knownAdapters, (fn, value) => {
if (fn) {
try {
Object.defineProperty(fn, 'name', {value});
} catch (e) {
// eslint-disable-next-line no-empty
}
Object.defineProperty(fn, 'adapterName', {value});
}
});
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)) {
adapter = knownAdapters[(id = String(nameOrAdapter)).toLowerCase()];
if (adapter === undefined) {
throw new AxiosError(`Unknown adapter '${id}'`);
}
}
if (adapter) {
break;
}
rejectedReasons[id || '#' + i] = adapter;
}
if (!adapter) {
const reasons = Object.entries(rejectedReasons)
.map(([id, state]) => `adapter ${id} ` +
(state === false ? 'is not supported by the environment' : 'is not available in the build')
);
let s = length ?
(reasons.length > 1 ? 'since :\n' + reasons.map(renderReason).join('\n') : ' ' + renderReason(reasons[0])) :
'as no adapter specified';
throw new AxiosError(
`There is no suitable adapter to dispatch the request ` + s,
'ERR_NOT_SUPPORT'
);
}
return adapter;
},
adapters: knownAdapters
}
2.1.2 设计思路
adapters.js 的设计目标是提供一个统一的接口来管理和获取不同的适配器。它将所有已知的适配器存储在 knownAdapters 对象中,并通过 getAdapter 方法根据传入的适配器名称或实例来获取合适的适配器。如果传入的适配器无效,会抛出相应的错误。
2.1.3 重点逻辑
- 适配器注册:将
http、xhr和fetch适配器注册到knownAdapters对象中。 - 适配器获取:
getAdapter方法会遍历传入的适配器列表,尝试找到合适的适配器。如果传入的是适配器名称,会从knownAdapters中查找;如果传入的是适配器实例,则直接使用。 - 错误处理:如果找不到合适的适配器,会抛出
AxiosError并给出详细的错误信息。
2.2 fetch.js
fetch.js 是基于浏览器原生 fetch API 实现的适配器。
2.2.1 关键代码
export default function fetchAdapter(config) {
return new Promise((resolve, reject) => {
const {
url,
method,
data,
headers,
responseType,
timeout,
signal
} = config;
const requestConfig = {
method: method.toUpperCase(),
headers: new Headers(headers),
body: data,
signal
};
if (timeout) {
const controller = new AbortController();
requestConfig.signal = controller.signal;
setTimeout(() => controller.abort(), timeout);
}
fetch(url, requestConfig)
.then(response => {
const responseData = {};
responseData.status = response.status;
responseData.statusText = response.statusText;
responseData.headers = response.headers;
const responseBody = responseType === 'text' ? response.text() : response[responseType]();
return responseBody.then(data => {
responseData.data = data;
resolve(responseData);
});
})
.catch(error => {
reject(error);
});
});
}
2.2.2 设计思路
fetch.js 的设计思路是利用浏览器原生的 fetch API 来发送 HTTP 请求。它将 Axios 的配置对象转换为 fetch 所需的配置,并处理请求的超时和响应数据的解析。
2.2.3 重点逻辑
- 请求配置:将 Axios 的配置对象转换为
fetch所需的配置,包括请求方法、请求头、请求体等。 - 超时处理:使用
AbortController来实现请求的超时功能。 - 响应处理:根据
responseType解析响应数据,并将结果封装成 Axios 所需的响应对象。
2.3 http.js
http.js 是基于 Node.js 的 http 和 https 模块实现的适配器。
2.3.1 关键代码
import http from 'http';
import https from 'https';
import url from 'url';
export default function httpAdapter(config) {
return new Promise((resolve, reject) => {
const parsedUrl = url.parse(config.url);
const protocol = parsedUrl.protocol === 'https:' ? https : http;
const options = {
hostname: parsedUrl.hostname,
port: parsedUrl.port,
path: parsedUrl.path,
method: config.method.toUpperCase(),
headers: config.headers
};
const req = protocol.request(options, res => {
const responseData = {};
responseData.status = res.statusCode;
responseData.statusText = res.statusMessage;
responseData.headers = res.headers;
let responseBuffer = [];
res.on('data', chunk => {
responseBuffer.push(chunk);
});
res.on('end', () => {
const responseBody = Buffer.concat(responseBuffer);
responseData.data = config.responseType === 'buffer' ? responseBody : responseBody.toString();
resolve(responseData);
});
});
req.on('error', error => {
reject(error);
});
if (config.data) {
req.write(config.data);
}
req.end();
});
}
2.3.2 设计思路
http.js 的设计思路是利用 Node.js 的 http 和 https 模块来发送 HTTP 请求。它将 Axios 的配置对象转换为 Node.js 请求所需的配置,并处理请求的发送和响应的接收。
2.3.3 重点逻辑
- 协议选择:根据请求的 URL 协议选择
http或https模块。 - 请求发送:创建请求对象,设置请求头和请求体,并发送请求。
- 响应处理:监听响应事件,收集响应数据,并将结果封装成 Axios 所需的响应对象。
2.3.4 架构全景
2.4 xhr.js
xhr.js 是基于浏览器原生 XMLHttpRequest 实现的适配器。
2.4.1 关键代码
export default function xhrAdapter(config) {
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
request.open(config.method.toUpperCase(), config.url, true);
Object.keys(config.headers).forEach(name => {
if (config.headers[name] !== undefined) {
request.setRequestHeader(name, config.headers[name]);
}
});
if (config.timeout) {
request.timeout = config.timeout;
}
request.onreadystatechange = function() {
if (request.readyState === 4) {
if (request.status >= 200 && request.status < 300) {
const responseData = {};
responseData.status = request.status;
responseData.statusText = request.statusText;
responseData.headers = parseHeaders(request.getAllResponseHeaders());
responseData.data = request.response;
resolve(responseData);
} else {
reject(new Error(`Request failed with status code ${request.status}`));
}
}
};
request.onerror = function() {
reject(new Error('Network Error'));
};
request.ontimeout = function() {
reject(new Error(`timeout of ${config.timeout}ms exceeded`));
};
request.send(config.data);
});
}
2.4.2 设计思路
xhr.js 的设计思路是利用浏览器原生的 XMLHttpRequest 对象来发送 HTTP 请求。它将 Axios 的配置对象转换为 XMLHttpRequest 所需的配置,并处理请求的超时和响应的解析。
2.4.3 重点逻辑
- 请求配置:设置请求方法、请求 URL、请求头和请求体。
- 事件监听:监听
readystatechange、error和timeout事件,处理请求的成功、失败和超时情况。 - 响应处理:将响应数据封装成 Axios 所需的响应对象。
三、总结
本文深入解析了 Axios 1.x 中适配器模块的核心源码,包括 adapters.js、fetch.js、http.js 和 xhr.js。通过对这些文件的分析,我们了解到 Axios 是如何通过适配器模式实现跨环境的 HTTP 请求的。adapters.js 作为适配器的管理中心,负责适配器的注册和获取;fetch.js、http.js 和 xhr.js 分别基于浏览器原生 fetch API、Node.js 的 http 和 https 模块以及浏览器原生 XMLHttpRequest 对象实现了不同环境下的请求发送。
通过阅读 Axios 的源码,我们不仅可以学习到如何设计一个灵活、可扩展的 HTTP 客户端,还可以了解到适配器模式在实际开发中的应用。同时,我们也能从 Axios 的错误处理、超时处理等细节中学习到如何编写健壮的代码。希望本文能帮助你更好地理解 Axios 的工作原理,提升你的前端开发技能。
- 点赞
- 收藏
- 关注作者
评论(0)