引言:AJAX并发请求的重要性与挑战
在现代Web应用中,AJAX(Asynchronous JavaScript and XML)技术已经成为构建动态、响应式用户界面的核心技术。随着应用复杂度的增加,前端往往需要同时发起多个异步请求来获取数据,例如一个典型的电商商品详情页可能需要同时请求商品信息、用户评论、推荐商品、库存状态等多个接口。这就涉及到了AJAX并发请求处理的问题。
AJAX并发请求处理面临着多个挑战:
浏览器并发限制
:浏览器对同一域名下的并发连接数有限制(通常为6-8个)
请求顺序不可控
:并发请求的完成顺序不确定,可能导致数据依赖问题
错误处理复杂
:多个请求中只要有一个失败,可能影响整个功能的可用性
性能优化
:如何合理控制并发数量,避免请求过多导致浏览器卡顿或服务器压力过大
本文将从基础原理出发,深入解析AJAX并发请求的处理机制,并通过实战技巧教你如何在高并发场景下优雅地处理这些挑战。
一、AJAX并发请求的基础原理
1.1 浏览器的并发连接限制
浏览器为了防止对服务器造成过大压力,对同一域名下的并发HTTP连接数进行了限制。这个限制因浏览器而异,但通常在6-8个左右。例如:
Chrome: 6个
Firefox: 6个
Safari: 6个
IE: 8个
这意味着即使我们同时发起10个AJAX请求,浏览器也只会同时建立6个连接,其余的请求需要等待前面的请求完成后才能开始。
1.2 HTTP/1.1与HTTP/2的并发机制差异
HTTP/1.1
:采用”请求-响应”模型,每个连接在同一时间只能处理一个请求。虽然可以通过多个TCP连接来实现并发,但受限于浏览器的同域名连接数限制。
HTTP/2
:支持多路复用(Multiplexing),允许在同一个TCP连接上同时传输多个请求和响应,突破了HTTP/1.1的并发连接限制。这使得在HTTP/2环境下,理论上可以同时处理大量请求。
1.3 异步事件循环与并发
JavaScript是单线程语言,通过事件循环(Event Loop)机制实现异步操作。当发起AJAX请求时:
浏览器创建XMLHttpRequest对象或使用fetch API
将请求放入任务队列
继续执行后续代码
当请求完成时,将回调函数放入微任务队列
在当前执行栈清空后执行回调
这种机制使得JavaScript可以在等待I/O操作时不阻塞主线程,从而实现”并发”效果。
二、原生JavaScript实现AJAX并发控制
2.1 Promise.all基础用法
Promise.all是最常用的并发控制方法,它接受一个Promise数组,当所有Promise都成功完成时返回结果,如果有一个失败则立即reject。
// 基础示例:同时请求多个接口
const fetchAllData = async () => {
try {
const [userData, productData, commentData] = await Promise.all([
fetch(‘/api/user/123’).then(res => res.json()),
fetch(‘/api/product/456’).then(res => res.json()),
fetch(‘/api/comments/456’).then(res => res.json())
]);
console.log(‘用户数据:’, userData);
console.log(‘商品数据:’, productData);
console.log(‘评论数据:’, commentData);
// 组合数据并更新UI
updateUI({userData, productData, commentData});
} catch (error) {
console.error(‘请求失败:’, error);
// 处理错误,显示错误提示
showError(‘数据加载失败,请重试’);
}
};
// 调用函数
fetchAllData();
优点
:
语法简洁,易于理解
一次性获取所有数据,便于后续处理
缺点
:
任何一个请求失败都会导致整个操作失败
无法获取部分成功的结果
无法限制并发数量
2.2 Promise.allSettled处理部分失败场景
当需要获取所有请求结果(无论成功或失败)时,可以使用Promise.allSettled:
const fetchAllDataWithSettled = async () => {
const results = await Promise.allSettled([
fetch(‘/api/user/123’).then(res => res.json()),
fetch(‘/api/product/456’).then(res => res.json()),
fetch(‘/api/comments/456’).then(res => res.json())
]);
const userData = results[0].status === ‘fulfilled’ ? results[0].value : null;
const productData = results[1].status === ‘fulfilled’ ? results[1].value : null;
const commentData = results[2].status === ‘fulfilled’ ? results[2].value : null;
// 即使部分失败也能继续处理
updateUI({userData, productData, commentData});
// 可以单独处理失败的请求
results.forEach((result, index) => {
if (result.status === ‘rejected’) {
console.error(`请求${index}失败:`, result.reason);
}
});
};
2.3 手动实现并发限制
当需要限制并发数量时,可以手动实现并发控制器:
/**
* 并发请求控制器
* @param {Array
* @param {number} maxConcurrency – 最大并发数
*/
function concurrentRequest(tasks, maxConcurrency) {
return new Promise((resolve, reject) => {
const results = [];
let running = 0; // 当前运行数量
let index = 0; // 当前执行到的任务索引
// 执行任务
function runTask() {
if (index >= tasks.length && running === 0) {
return resolve(results);
}
while (running < maxConcurrency && index < tasks.length) {
const currentTask = tasks[index];
const currentIndex = index;
index++;
running++;
currentTask()
.then(res => {
results[currentIndex] = { status: ‘fulfilled’, value: res };
})
.catch(err => {
results[currentIndex] = { status: ‘rejected’, reason: err };
})
.finally(() => {
running–;
runTask(); // 继续执行下一个任务
});
}
}
// 启动
runTask();
});
}
// 使用示例
const tasks = [
() => fetch(‘/api/data1’).then(res => res.json()),
() => fetch(‘/api/data2’).then(res => res.json()),
() => fetch(‘/api/data3’).then(res => res.json()),
() => fetch(‘/api/data4’).then(res => res.json()),
() => fetch(‘/api/data5’).then(res => res.json()),
() => fetch(‘/api/data6’).then(res => res.json()),
() => fetch(‘/api/data7’).then(res => res.json()),
() => fetch(‘/api/data8’).then(res => res.json())
];
// 限制同时最多3个请求
concurrentRequest(tasks, 3).then(results => {
console.log(‘所有请求完成:’, results);
});
2.4 使用async-pool库
社区已经有成熟的并发控制库,如async-pool:
// 安装: npm install async-pool
import asyncPool from ‘async-pool’;
const results = await asyncPool(
3, // 并发数
[1, 2, 3, 4, 5, 6, 7, 8], // 数据数组
async (id) => {
const response = await fetch(`/api/data/${id}`);
return response.json();
}
);
console.log(results); // [data1, data2, …]
三、高并发场景下的实战技巧
3.1 请求合并(Batch Requests)
当需要请求多个资源时,可以考虑将多个请求合并为一个请求,减少网络开销。
场景
:获取多个用户的详细信息
// 不好的做法:分别请求
const getUserInfo = async (userId) => {
const user = await fetch(`/api/user/${userId}`).then(res => res.json());
const profile = await fetch(`/api/user/${userId}/profile`).then(res => res.json());
const settings = await fetch(`/api/user/${userId}/settings`).then(res => res.json());
return { user, profile, settings };
};
// 好的做法:合并请求
const getUserInfoBatch = async (userIds) => {
// 后端提供批量查询接口
const response = await fetch(‘/api/users/batch’, {
method: ‘POST’,
headers: { ‘Content-Type’: ‘application/json’ },
body: JSON.stringify({ userIds, fields: [‘profile’, ‘settings’] })
});
return response.json();
};
// 使用示例
const users = await getUserInfoBatch([1, 2, 3, 4, 5]);
3.2 缓存策略优化
对于重复请求,使用缓存可以显著减少网络开销。
// 简单的内存缓存实现
const requestCache = new Map();
async function cachedFetch(url, options = {}) {
const cacheKey = JSON.stringify({ url, options });
// 检查缓存
if (requestCache.has(cacheKey)) {
const cached = requestCache.get(cacheKey);
// 检查缓存是否过期(例如5分钟)
if (Date.now() – cached.timestamp < 5 * 60 * 1000) {
return cached.data;
}
}
// 发起请求
const data = await fetch(url, options).then(res => res.json());
// 存入缓存
requestCache.set(cacheKey, {
data,
timestamp: Date.now()
});
return data;
}
// 使用示例
const productInfo = await cachedFetch(‘/api/product/123’);
3.3 请求取消与竞态处理
在高并发场景下,用户可能快速切换页面或触发新的请求,需要取消之前的请求以避免竞态条件。
// 使用AbortController取消请求
class RequestManager {
constructor() {
this.controllers = new Map();
}
async request(url, options = {}) {
// 取消同一key的先前请求
const key = options.key || url;
this.cancel(key);
const controller = new AbortController();
this.controllers.set(key, controller);
try {
const response = await fetch(url, {
…options,
signal: controller.signal
});
const data = await response.json();
this.controllers.delete(key);
return data;
} catch (error) {
if (error.name === ‘AbortError’) {
console.log(‘请求被取消’);
return null;
}
throw error;
}
}
cancel(key) {
const controller = this.controllers.get(key);
if (controller) {
controller.abort();
this.controllers.delete(key);
}
}
cancelAll() {
this.controllers.forEach(controller => controller.abort());
this.controllers.clear();
}
}
// 使用示例
const requestManager = new RequestManager();
// 用户搜索时,取消之前的搜索请求
async function handleSearch(keyword) {
const results = await requestManager.request(`/api/search?q=${keyword}`, {
key: ‘search’
});
if (results) {
updateSearchResults(results);
}
}
3.4 重试机制与熔断策略
对于网络不稳定的情况,实现重试机制可以提高请求成功率。
/**
* 带重试的请求函数
* @param {Function} requestFn – 请求函数
* @param {number} maxRetries – 最大重试次数
* @param {number} delay – 重试延迟(毫秒)
*/
async function retryRequest(requestFn, maxRetries = 3, delay = 1000) {
for (let i = 0; i <= maxRetries; i++) {
try {
return await requestFn();
} catch (error) {
if (i === maxRetries) throw error;
console.log(`请求失败,${delay}ms后重试...`);
await new Promise(resolve => setTimeout(resolve, delay));
// 指数退避:每次重试延迟加倍
delay *= 2;
}
}
}
// 使用示例
const fetchDataWithRetry = async () => {
try {
const data = await retryRequest(
() => fetch(‘/api/unstable’).then(res => res.json()),
3,
1000
);
console.log(‘成功获取数据:’, data);
} catch (error) {
console.error(‘重试多次后仍然失败:’, error);
}
};
// 简单的熔断器实现
class CircuitBreaker {
constructor(failureThreshold = 5, resetTimeout = 60000) {
this.failureCount = 0;
this.lastFailureTime = null;
this.state = ‘CLOSED’; // CLOSED, OPEN, HALF_OPEN
this.failureThreshold = failureThreshold;
this.resetTimeout = resetTimeout;
}
async call(requestFn) {
if (this.state === ‘OPEN’) {
const now = Date.now();
if (now – this.lastFailureTime > this.resetTimeout) {
this.state = ‘HALF_OPEN’;
} else {
throw new Error(‘Circuit breaker is OPEN’);
}
}
try {
const result = await requestFn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failureCount = 0;
this.state = ‘CLOSED’;
}
onFailure() {
this.failureCount++;
this.lastFailureTime = Date.now();
if (this.failureCount >= this.failureThreshold) {
this.state = ‘OPEN’;
console.error(`熔断器已打开,将在${this.resetTimeout}ms后尝试恢复`);
}
}
}
// 使用示例
const breaker = new CircuitBreaker(3, 10000); // 3次失败后打开,10秒后尝试恢复
async function fetchWithCircuitBreaker() {
try {
const data = await breaker.call(() =>
fetch(‘/api/critical’).then(res => res.json())
);
return data;
} catch (error) {
console.error(‘请求失败:’, error.message);
// 降级处理:返回缓存数据或默认值
return getFallbackData();
}
}
3.5 优先级调度
在高并发场景下,某些请求比其他请求更重要,需要优先处理。
// 优先级队列实现
class PriorityQueue {
constructor() {
this.items = [];
}
enqueue(element, priority) {
const queueElement = { element, priority };
let added = false;
for (let i = 0; i < this.items.length; i++) {
if (queueElement.priority > this.items[i].priority) {
this.items.splice(i, 0, queueElement);
added = true;
break;
}
}
if (!added) {
this.items.push(queueElement);
}
}
dequeue() {
return this.items.shift();
}
isEmpty() {
return this.items.length === 0;
}
}
// 优先级并发控制器
class PriorityRequestController {
constructor(maxConcurrency = 3) {
this.maxConcurrency = maxConcurrency;
this.running = 0;
this.queue = new PriorityQueue();
}
async add(requestFn, priority = 0) {
return new Promise((resolve, reject) => {
this.queue.enqueue({ requestFn, resolve, reject }, priority);
this.process();
});
}
async process() {
if (this.running >= this.maxConcurrency || this.queue.isEmpty()) {
return;
}
const { element } = this.queue.dequeue();
this.running++;
try {
const result = await element.requestFn();
element.resolve(result);
} catch (error) {
element.reject(error);
} finally {
this.running–;
this.process(); // 继续处理下一个
}
}
}
// 使用示例
const controller = new PriorityRequestController(2);
// 高优先级请求(优先级10)
controller.add(
() => fetch(‘/api/critical-data’).then(res => res.json()),
10
).then(data => {
console.log(‘关键数据:’, data);
});
// 普通优先级请求(优先级5)
controller.add(
() => fetch(‘/api/normal-data’).then(res => res.json()),
5
).then(data => {
console.log(‘普通数据:’, data);
});
// 低优先级请求(优先级1)
controller.add(
() => fetch(‘/api/lazy-data’).then(res => res.json()),
1
).then(data => {
console.log(‘延迟数据:’, data);
});
四、现代框架中的并发处理方案
4.1 React中的并发请求处理
在React中,结合Suspense和React Query可以优雅地处理并发请求。
import { useQuery, QueryClient, QueryClientProvider } from ‘react-query’;
import { Suspense } from ‘react’;
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5分钟缓存
cacheTime: 10 * 60 * 1000, // 10分钟缓存
},
},
});
// 商品详情组件
function ProductDetail({ productId }) {
// 并行发起多个查询
const { data: product } = useQuery([‘product’, productId], () =>
fetch(`/api/product/${productId}`).then(res => res.json())
);
const { data: comments } = useQuery([‘comments’, productId], () =>
fetch(`/api/product/${productId}/comments`).then(res => res.json())
);
const { data: recommendations } = useQuery([‘recommendations’, productId], () =>
fetch(`/api/product/${productId}/recommendations`).then(res => res.json())
);
// React Query会自动处理并发、缓存、重试等
if (!product || !comments || !recommendations) {
return
;
}
return (
{product.name}
);
}
// 使用Suspense处理加载状态
function App() {
return (