Ajax 类代码详解
下面对给定的 Ajax 类代码进行逐行分析,并结合示例说明各部分功能和作用。
导入依赖
import defaultConfig from './config.js';:引入默认配置对象defaultConfig,通常包含基础 URL、超时时间等基础设置,例如{ baseURL: "https://api.example.com", timeout: 5000 }。后续实例化时会与用户传入的配置合并。import { isString } from '../utils/type.js';:引入isString工具函数,用于判断传入值是否为字符串。该函数用于后续判断请求参数是否为字符串,比如isString("hello")返回true,isString(123)返回false。import Interceptor from './interceptor.js';:引入Interceptor类,用来创建请求和响应的拦截器对象。拦截器允许在请求发出前或响应到达后执行自定义逻辑(如添加请求头、日志记录等)。例如,可以通过ajax.interceptors.request.use(config => {/*修改config*/return config;})注册请求拦截器。import doRequest from './doRequest.js';:引入doRequest函数,负责执行实际的 HTTP 请求(可能采用fetch或XMLHttpRequest)。doRequest接受请求配置并返回一个 Promise,对应请求完成后的响应。import CustomEventSource from './CustomEventSource.js';:引入CustomEventSource类,用于处理服务器推送事件(SSE)。在下面的createEventSource方法中,会用它来创建基于fetch的事件源,用于监听后端持续推送的数据。
Ajax 类与构造函数
class Ajax {
constructor(instanceConfig = {}) {
// 实例默认配置
this.defaults = Object.assign({}, defaultConfig, instanceConfig);
this.interceptors = {
request: new Interceptor(),
response: new Interceptor()
};
}
// ...
}
-
constructor(instanceConfig = {}):构造函数接受一个可选的实例配置对象instanceConfig(默认空对象)。 -
this.defaults = Object.assign({}, defaultConfig, instanceConfig);:使用Object.assign()将一个空对象{}依次合并defaultConfig和instanceConfig。这会将默认配置和实例配置的属性复制到新对象中,并赋值给this.defaults。例如,如果defaultConfig是{ baseURL: "https://api", timeout: 5000 },instanceConfig是{ timeout: 10000 },合并后this.defaults变为{ baseURL: "https://api", timeout: 10000 }。 -
this.interceptors = { request: new Interceptor(), response: new Interceptor() };:为当前 Ajax 实例创建两个拦截器对象,分别管理请求拦截器和响应拦截器。这样可以在请求发出前或响应返回后统一处理逻辑。拦截器通常有一个use方法用于注册回调,例如:ajax.interceptors.request.use( config => { config.headers['X-Test'] = 'value'; return config; }, error => Promise.reject(error) ); ajax.interceptors.response.use( response => { console.log('Response arrived'); return response; }, error => Promise.reject(error) );上述示例中,请求拦截器将给每个请求添加
X-Test头部,响应拦截器则在每次响应到达时打印日志。
通用请求方法 request
async request(requestConfig, otherConfig) {
const config = {};
// 参数规范化
if (isString(requestConfig)) {
Object.assign(config, this.defaults, {
url: requestConfig
}, otherConfig);
} else {
Object.assign(config, this.defaults, requestConfig);
}
if (!config.url.startsWith('http')) {
config.url = config.baseURL + config.url;
}
config.method = config.method.toUpperCase();
config.responseType = config.responseType.toLowerCase();
// 请求拦截器队列处理
const requestInterceptorChain = [];
this.interceptors.request.forEach(function(interceptor) {
requestInterceptorChain.unshift(interceptor.resolved, interceptor.rejected);
});
// 响应拦截器队列处理
const responseInterceptorChain = [];
this.interceptors.response.forEach(function(interceptor) {
responseInterceptorChain.push(interceptor.resolved, interceptor.rejected);
});
// 队列处理
const taskChain = [doRequest.bind(this), undefined];
taskChain.unshift(...requestInterceptorChain);
taskChain.push(...responseInterceptorChain);
let i = 0;
let len = taskChain.length;
let promise = Promise.resolve(config);
while (i < len) {
promise = promise.then(taskChain[i++], taskChain[i++]);
}
return promise;
}
-
方法声明为
async(异步),所以它会返回一个 Promise 对象。调用此方法时,最终会返回一个 Promise,可以使用await或.then()来处理请求结果。 -
参数规范化:先创建一个空的
config对象,然后判断传入的requestConfig类型。-
如果
requestConfig是字符串(通过isString判断),则视为 URL 字符串,执行:Object.assign(config, this.defaults, { url: requestConfig }, otherConfig);这句将默认配置、一个包含
url属性的对象,以及可选的otherConfig合并到config中。例如,调用ajax.request('/users', { method: 'GET' })时,会得到{ ..., url: '/users', method: 'GET' }。 -
否则,
requestConfig被视为配置对象,执行:Object.assign(config, this.defaults, requestConfig);这会合并默认配置和用户传入的配置(包括
url、method、data等)。 -
在任一情况下,合并后
config包含默认设置和指定的请求选项。Object.assign会覆盖同名属性。
-
-
处理 URL:如果
config.url不以"http"开头(判断使用startsWith方法),则将默认的baseURL(来自this.defaults.baseURL)拼接到 URL 前面,如:if (!config.url.startsWith('http')) { config.url = config.baseURL + config.url; }例如默认
baseURL为"https://api.example.com",请求url为"/users",则最终config.url变为"https://api.example.com/users"。 -
规范化大小写:
config.method = config.method.toUpperCase();将 HTTP 方法名转换为全大写(toUpperCase()返回字符串的大写形式)。例如"get"变成"GET"。config.responseType = config.responseType.toLowerCase();则将响应类型转换为小写(toLowerCase()返回小写字符串),确保一致性。 -
拦截器队列:将所有注册的请求拦截器和响应拦截器组织成数组。
-
请求拦截器队列:
const requestInterceptorChain = []; this.interceptors.request.forEach(function(interceptor) { requestInterceptorChain.unshift(interceptor.resolved, interceptor.rejected); });这段代码遍历
request拦截器对象中的每个拦截器,将它们的resolved(成功回调)和rejected(失败回调)函数,按照“后注册的拦截器先执行”的顺序(使用unshift逆序添加)推入requestInterceptorChain数组中。 -
响应拦截器队列类似,只是将回调函数按注册顺序(
push)加入responseInterceptorChain:const responseInterceptorChain = []; this.interceptors.response.forEach(function(interceptor) { responseInterceptorChain.push(interceptor.resolved, interceptor.rejected); });
这样一来,
requestInterceptorChain和responseInterceptorChain分别存放了所有拦截器回调函数,供后续链式调用。 -
-
执行链式调用:
const taskChain = [doRequest.bind(this), undefined]; taskChain.unshift(...requestInterceptorChain); taskChain.push(...responseInterceptorChain); let promise = Promise.resolve(config); while (i < taskChain.length) { promise = promise.then(taskChain[i++], taskChain[i++]); } return promise;首先
taskChain初始为[doRequest.bind(this), undefined],其中doRequest.bind(this)是绑定当前this的请求发送函数,undefined用于占位捕获错误。然后将请求拦截器插入头部、响应拦截器插入尾部,形成完整的调用链。Promise.resolve(config)创建了一个已解决的 Promise,以config作为初始值。在循环中,依次从taskChain中取出回调函数对来调用.then(resolved, rejected),从而构建出完整的 Promise 链:先执行所有请求拦截器,再执行doRequest发起实际请求,最后执行所有响应拦截器。整个过程返回一个最终的 Promise,对应最终的响应结果。例如:ajax.interceptors.request.use( config => { console.log('准备发送请求:', config.url); return config; } ); ajax.interceptors.response.use( response => { console.log('收到响应'); return response; } ); ajax.get('/users');上面示例中,在真正发起请求前会先打印出要请求的 URL(请求拦截器处理),请求完成后打印“收到响应”(响应拦截器处理),最后将
response对象返回给调用方。
JSONP 方法
async jsonp(url, data, config) {
return this.request({
method: 'GET',
url,
data,
...config,
responseType: 'jsonp'
});
}
-
jsonp方法是对request的封装,用于发送 JSONP 请求。它固定使用GET方法,将用户传入的url、data(查询参数等)和额外配置合并,并强制将responseType设置为'jsonp'。 -
JSONP(JSON with Padding)是一种通过
<script>标签跨域获取 JSON 数据的传统技术。例如:ajax.jsonp('https://api.example.com/data?callback=cb', { foo: 'bar' }).then(data => { console.log('JSONP 返回的数据:', data); });上例中,实际发送的是对
https://api.example.com/data?callback=cb&foo=bar的<script>请求,不受同源策略限制,服务器需要返回调用cb(...)的 JavaScript 片段。responseType: 'jsonp'会告诉doRequest以 JSONP 的方式处理响应。
事件源方法 createEventSource
createEventSource(url, requestConfig) {
const _ = this;
const controller = new AbortController();
const config = Object.assign({}, _.defaults, requestConfig, {
url,
method: 'GET',
headers: {
'Accept': 'text/event-stream'
},
responseType: 'stream',
signal: controller.signal
});
return new CustomEventSource(function(){
return _.request(config);
},{
...config,
controller
});
}
-
该方法用于创建一个基于
fetch的自定义事件源(类似浏览器的EventSource),以监听服务器发送的持续事件流(SSE)。 -
首先,
const controller = new AbortController();创建一个AbortController实例。这个控制器用于在需要时中断异步操作(如取消请求)。 -
接着使用
Object.assign合并默认配置和传入的配置,构造最终的请求配置config。在其中强制设置:url和method: 'GET'(SSE 通常使用 GET)- 请求头
Accept: 'text/event-stream',告知服务器使用事件流格式 responseType: 'stream'表示期望返回可读流signal: controller.signal将AbortController的信号对象加入配置,以便后续可以通过controller.abort()取消请求。
-
最后,返回一个新的
CustomEventSource实例。它接收一个函数(用于获取 Promise,如调用_.request(config)发起请求)和一个配置对象(包括controller本身)。使用方法示例:const source = ajax.createEventSource('/events', { baseURL: 'https://api.example.com' }); source.addEventListener('message', event => { console.log('收到事件:', event.data); }); // 需要停止时可以调用: source.controller.abort();在这个示例中,调用
ajax.createEventSource后会发起请求并持续监听服务器推送的事件,message事件触发时可以在回调中处理数据。通过source.controller.abort()可以随时取消连接。
快捷方法封装
最后一段代码定义了对常用 HTTP 方法的简易封装:
['get', 'post', 'push', 'patch'].forEach(function(method){
Ajax.prototype[method] = function(url, data, config) {
return this.request({
method,
url,
data,
...config
});
};
});
['delete', 'head', 'options'].forEach(function(method){
Ajax.prototype[method] = function(url, config) {
return this.request({
method,
url,
data: null,
...config
});
};
});
-
第一组
['get', 'post', 'push', 'patch']:为每个方法在原型上定义对应函数,使之简写调用request。例如:// 等效于 ajax.request({method: 'get', url: '/users', data: {id: 1}}) ajax.get('/users', {id: 1}); // 等效于 ajax.request({method: 'post', url: '/users', data: {name: 'Alice'}}) ajax.post('/users', {name: 'Alice'});这里注意代码中写的是
'push',一般应为'put'(更新数据),但逻辑相同。调用时传递url、data(请求体或参数)以及可选的config对象,最终传给request方法处理。 -
第二组
['delete', 'head', 'options']:这些方法不需要请求体(data),因此函数只接受url和config。它们在调用request时会强制data: null,例如:// 等效于 ajax.request({method: 'delete', url: '/users/123', data: null}) ajax.delete('/users/123'); // 等效于 ajax.request({method: 'options', url: '/users', data: null}) ajax.options('/users');这样用户可以直接
ajax.delete(url)、ajax.head(url)等简洁地发起请求,无需自己写完整的配置。
以上就是代码中每个部分的详细说明。最后一句 export default Ajax; 将 Ajax 类作为模块的默认导出,以供其他文件通过 import Ajax from './Ajax.js' 引用使用。
参考文献: 本解释参考了 JavaScript 官方文档和相关技术资料。