AJAX請(qǐng)求次數(shù)過(guò)多的四種解決方案
引言
在前端開(kāi)發(fā)中,AJAX作為異步通信的核心技術(shù),極大提升了頁(yè)面交互體驗(yàn)。但在實(shí)際項(xiàng)目中,我們常面臨請(qǐng)求次數(shù)過(guò)多的問(wèn)題——比如批量獲取數(shù)據(jù)、多模塊并行加載時(shí),一次性發(fā)起數(shù)十甚至上百次請(qǐng)求,不僅會(huì)觸發(fā)瀏覽器并發(fā)限制、導(dǎo)致請(qǐng)求阻塞,還可能給服務(wù)器帶來(lái)過(guò)大壓力,引發(fā)超時(shí)、報(bào)錯(cuò)等問(wèn)題。今天就為大家梳理四種實(shí)戰(zhàn)性極強(qiáng)的解決方案,幫你優(yōu)雅處理AJAX請(qǐng)求過(guò)量問(wèn)題。
一、請(qǐng)求次數(shù)過(guò)多到底有什么危害?
- 瀏覽器層面:HTTP/1.1協(xié)議下,瀏覽器對(duì)同一域名的并發(fā)請(qǐng)求數(shù)限制為6個(gè)(不同瀏覽器略有差異),超出的請(qǐng)求會(huì)進(jìn)入隊(duì)列等待,導(dǎo)致頁(yè)面加載緩慢、交互卡頓;
- 服務(wù)器層面:短時(shí)間內(nèi)大量請(qǐng)求會(huì)占用服務(wù)器CPU、內(nèi)存等資源,可能觸發(fā)限流策略,甚至導(dǎo)致服務(wù)不可用;
- 開(kāi)發(fā)維護(hù)層面:大量零散請(qǐng)求的錯(cuò)誤處理、狀態(tài)管理會(huì)增加代碼復(fù)雜度,后期排查問(wèn)題時(shí)也難以定位。
二、四大核心解決方案實(shí)戰(zhàn)解析
針對(duì)不同場(chǎng)景(如是否需要一次性獲取數(shù)據(jù)、對(duì)加載速度的要求等),我們有四種不同的解決方案,下面逐一拆解其原理、代碼實(shí)現(xiàn)和適用場(chǎng)景。
方案一:串行執(zhí)行——穩(wěn)妥的“逐個(gè)處理”策略
串行執(zhí)行的核心邏輯是:一個(gè)請(qǐng)求完成后,再發(fā)起下一個(gè)請(qǐng)求,避免同時(shí)發(fā)起大量請(qǐng)求導(dǎo)致的阻塞。這種方案的優(yōu)勢(shì)是穩(wěn)定性極高,不會(huì)給服務(wù)器帶來(lái)突發(fā)壓力,缺點(diǎn)是總耗時(shí)較長(zhǎng)(等于所有請(qǐng)求耗時(shí)之和)。
適用場(chǎng)景
服務(wù)器抗壓能力較弱、請(qǐng)求之間有依賴(lài)關(guān)系(如后一個(gè)請(qǐng)求需要前一個(gè)請(qǐng)求的返回結(jié)果)、對(duì)總耗時(shí)要求不高的場(chǎng)景。
代碼實(shí)現(xiàn)(基于Promise+async/await)
我們以批量獲取100條用戶(hù)數(shù)據(jù)為例,封裝串行請(qǐng)求函數(shù):
/**
* 串行執(zhí)行AJAX請(qǐng)求
* @param {Array} urls - 請(qǐng)求地址列表(如['/api/user/1', '/api/user/2', ..., '/api/user/100'])
* @returns {Promise<Array>} 所有請(qǐng)求結(jié)果的數(shù)組(順序與urls一致)
*/
async function serialRequest(urls) {
const results = []; // 存儲(chǔ)最終結(jié)果
const maxRetry = 2; // 失敗重試次數(shù),提升穩(wěn)定性
for (let i = 0; i < urls.length; i++) {
const url = urls[i];
let retryCount = 0;
let success = false;
// 失敗重試邏輯
while (retryCount <= maxRetry && !success) {
try {
const response = await fetch(url, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
signal: AbortSignal.timeout(5000) // 5秒超時(shí),避免無(wú)限等待
});
if (!response.ok) {
throw new Error(`HTTP錯(cuò)誤,狀態(tài)碼:${response.status}`);
}
const data = await response.json();
results.push(data);
success = true;
console.log(`第${i+1}個(gè)請(qǐng)求成功,進(jìn)度:${i+1}/${urls.length}`);
} catch (error) {
retryCount++;
if (retryCount > maxRetry) {
results.push(null); // 標(biāo)記失敗的請(qǐng)求
console.error(`第${i+1}個(gè)請(qǐng)求失?。ㄖ卦?{maxRetry}次):`, error.message);
} else {
console.log(`第${i+1}個(gè)請(qǐng)求失敗,正在重試(${retryCount}/${maxRetry})`);
await new Promise(resolve => setTimeout(resolve, 1000)); // 重試間隔1秒
}
}
}
}
return results;
}
// 用法示例
const urlList = Array.from({ length: 100 }, (_, i) => `/api/user/${i+1}`);
serialRequest(urlList).then(allResults => {
const successCount = allResults.filter(Boolean).length;
console.log(`所有請(qǐng)求完成,成功${successCount}個(gè),失敗${100 - successCount}個(gè)`);
// 后續(xù)處理結(jié)果...
});方案二:Promise并行控制——高效的“分批并發(fā)”策略
并行控制并非“一次性發(fā)起所有請(qǐng)求”,而是限制并發(fā)數(shù)量(如同時(shí)發(fā)起5個(gè)請(qǐng)求),當(dāng)其中一個(gè)請(qǐng)求完成后,再補(bǔ)充一個(gè)新的請(qǐng)求進(jìn)入并發(fā)隊(duì)列。這種方案兼顧了效率和穩(wěn)定性,總耗時(shí)遠(yuǎn)短于串行執(zhí)行,又不會(huì)觸發(fā)瀏覽器或服務(wù)器的限制。
適用場(chǎng)景
服務(wù)器能承受一定并發(fā)壓力、請(qǐng)求之間無(wú)依賴(lài)關(guān)系、對(duì)加載速度有較高要求的場(chǎng)景(如批量導(dǎo)出數(shù)據(jù)、多模塊數(shù)據(jù)并行加載)。
代碼實(shí)現(xiàn)(基于Promise.race)
核心是通過(guò)“并發(fā)池”管理正在執(zhí)行的請(qǐng)求,用Promise.race監(jiān)聽(tīng)并發(fā)池中請(qǐng)求的完成狀態(tài),實(shí)現(xiàn)動(dòng)態(tài)補(bǔ)充請(qǐng)求:
/**
* 帶并發(fā)控制的并行請(qǐng)求
* @param {Array} urls - 請(qǐng)求地址列表
* @param {number} limit - 最大并發(fā)數(shù)(推薦3-5,根據(jù)服務(wù)器性能調(diào)整)
* @returns {Promise<Array>} 所有請(qǐng)求結(jié)果的數(shù)組
*/
async function concurrentRequest(urls, limit = 5) {
const results = []; // 存儲(chǔ)最終結(jié)果
const executing = new Set(); // 并發(fā)池:存儲(chǔ)正在執(zhí)行的Promise
const urlQueue = [...urls]; // 請(qǐng)求隊(duì)列
// 單個(gè)請(qǐng)求的封裝函數(shù)
const request = async (url, index) => {
try {
const response = await fetch(url, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
signal: AbortSignal.timeout(5000)
});
if (!response.ok) {
throw new Error(`HTTP錯(cuò)誤,狀態(tài)碼:${response.status}`);
}
const data = await response.json();
results[index] = data; // 按原順序存儲(chǔ)結(jié)果
console.log(`請(qǐng)求${url}成功`);
} catch (error) {
results[index] = null;
console.error(`請(qǐng)求${url}失敗:`, error.message);
} finally {
executing.delete(url); // 請(qǐng)求完成后移出并發(fā)池
// 隊(duì)列中有剩余請(qǐng)求時(shí),補(bǔ)充到并發(fā)池
if (urlQueue.length > 0) {
const nextUrl = urlQueue.shift();
const nextIndex = urls.indexOf(nextUrl);
executing.add(request(nextUrl, nextIndex));
}
}
};
// 初始化并發(fā)池
for (let i = 0; i < Math.min(limit, urls.length); i++) {
const url = urlQueue.shift();
executing.add(request(url, i));
}
// 等待所有請(qǐng)求完成
await Promise.all(executing);
return results;
}
// 用法示例
const urlList = Array.from({ length: 100 }, (_, i) => `/api/user/${i+1}`);
concurrentRequest(urlList, 5).then(allResults => {
// 處理結(jié)果...
});方案三:列表分頁(yè)——按需加載的“分段獲取”策略
分頁(yè)是前端處理大量數(shù)據(jù)的經(jīng)典方案,核心邏輯是:將100條數(shù)據(jù)拆分為多頁(yè)(如每頁(yè)10條),只獲取用戶(hù)當(dāng)前需要查看的頁(yè)面數(shù)據(jù),通過(guò)“上一頁(yè)/下一頁(yè)”或“頁(yè)碼選擇”觸發(fā)新的請(qǐng)求。這種方案從根源上減少了單次請(qǐng)求數(shù)量,是數(shù)據(jù)展示類(lèi)場(chǎng)景的首選。
適用場(chǎng)景
表格數(shù)據(jù)展示、列表數(shù)據(jù)瀏覽等場(chǎng)景(如后臺(tái)管理系統(tǒng)的用戶(hù)列表、電商平臺(tái)的商品列表)。
代碼實(shí)現(xiàn)(結(jié)合前端分頁(yè)控件)
這里以“每頁(yè)10條,共10頁(yè)”為例,實(shí)現(xiàn)基礎(chǔ)分頁(yè)功能:
// 分頁(yè)核心狀態(tài)
const pagination = {
pageNum: 1, // 當(dāng)前頁(yè)碼
pageSize: 10, // 每頁(yè)條數(shù)
total: 100, // 總數(shù)據(jù)量(可從接口返回)
totalPages: 10 // 總頁(yè)數(shù)
};
// 渲染分頁(yè)數(shù)據(jù)
function renderTable(data) {
const tableBody = document.getElementById('table-body');
tableBody.innerHTML = data.map(item => `
<tr>
<td>${item.id}</td>
<td>${item.name}</td>
<td>${item.phone}</td>
</tr>
`).join('');
}
// 加載指定頁(yè)數(shù)據(jù)
async function loadPageData(pageNum) {
try {
const response = await fetch(`/api/users?pageNum=${pageNum}&pageSize=${pagination.pageSize}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
if (!response.ok) {
throw new Error('請(qǐng)求失敗');
}
const { data } = await response.json();
renderTable(data);
pagination.pageNum = pageNum; // 更新當(dāng)前頁(yè)碼
} catch (error) {
console.error('加載分頁(yè)數(shù)據(jù)失?。?, error.message);
}
}
// 綁定分頁(yè)控件事件(上一頁(yè)/下一頁(yè)/頁(yè)碼點(diǎn)擊)
document.getElementById('prev-page').addEventListener('click', () => {
if (pagination.pageNum > 1) {
loadPageData(pagination.pageNum - 1);
}
});
document.getElementById('next-page').addEventListener('click', () => {
if (pagination.pageNum < pagination.totalPages) {
loadPageData(pagination.pageNum + 1);
}
});
// 初始化加載第一頁(yè)
loadPageData(1);前端分頁(yè)的關(guān)鍵是與后端約定好分頁(yè)參數(shù)(pageNum頁(yè)碼、pageSize每頁(yè)條數(shù)),后端返回對(duì)應(yīng)頁(yè)的數(shù)據(jù)和總條數(shù),前端再根據(jù)總條數(shù)計(jì)算總頁(yè)數(shù),實(shí)現(xiàn)分頁(yè)控件的聯(lián)動(dòng)。
方案四:內(nèi)容懶加載——智能的“滾動(dòng)觸發(fā)”策略
懶加載(Lazy Load)是一種“被動(dòng)加載”策略,核心邏輯是:只有當(dāng)數(shù)據(jù)進(jìn)入或即將進(jìn)入瀏覽器視口時(shí),才發(fā)起請(qǐng)求獲取數(shù)據(jù),常見(jiàn)于長(zhǎng)列表、圖片列表等場(chǎng)景。這種方案能最大限度減少初始加載的請(qǐng)求數(shù)量,提升頁(yè)面首屏加載速度。
適用場(chǎng)景
無(wú)限滾動(dòng)列表(如社交媒體的動(dòng)態(tài)流)、圖片密集型頁(yè)面(如相冊(cè)、商品詳情頁(yè)的圖片列表)、首屏加載速度要求高的場(chǎng)景。
代碼實(shí)現(xiàn)(基于滾動(dòng)監(jiān)聽(tīng))
我們以無(wú)限滾動(dòng)的用戶(hù)列表為例,當(dāng)用戶(hù)滾動(dòng)到頁(yè)面底部時(shí),自動(dòng)加載下一頁(yè)數(shù)據(jù):
// 懶加載核心狀態(tài)
const lazyLoadState = {
pageNum: 1,
pageSize: 10,
isLoading: false, // 防止重復(fù)請(qǐng)求
hasMore: true // 是否還有更多數(shù)據(jù)
};
// 渲染列表數(shù)據(jù)
function renderList(data) {
const listContainer = document.getElementById('list-container');
data.forEach(item => {
const listItem = document.createElement('div');
listItem.className = 'list-item';
listItem.innerHTML = `<h4>${item.name}</h4><p>${item.desc}</p>`;
listContainer.appendChild(listItem);
});
}
// 加載下一頁(yè)數(shù)據(jù)
async function loadNextPage() {
if (lazyLoadState.isLoading || !lazyLoadState.hasMore) return;
lazyLoadState.isLoading = true;
try {
const response = await fetch(`/api/users?pageNum=${lazyLoadState.pageNum}&pageSize=${lazyLoadState.pageSize}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
if (!response.ok) {
throw new Error('請(qǐng)求失敗');
}
const { data, total } = await response.json();
renderList(data);
// 判斷是否還有更多數(shù)據(jù)
const loadedTotal = (lazyLoadState.pageNum) * lazyLoadState.pageSize;
lazyLoadState.hasMore = loadedTotal < total;
// 更新頁(yè)碼
lazyLoadState.pageNum++;
} catch (error) {
console.error('加載數(shù)據(jù)失?。?, error.message);
} finally {
lazyLoadState.isLoading = false;
}
}
// 監(jiān)聽(tīng)滾動(dòng)事件,觸發(fā)懶加載
window.addEventListener('scroll', () => {
// 計(jì)算滾動(dòng)距離:視口高度 + 滾動(dòng)條滾動(dòng)距離 >= 文檔高度 - 觸發(fā)閾值(如100px)
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
const clientHeight = document.documentElement.clientHeight;
const scrollHeight = document.documentElement.scrollHeight;
if (scrollTop + clientHeight >= scrollHeight - 100) {
loadNextPage();
}
});
// 初始化加載第一頁(yè)
loadNextPage();進(jìn)階優(yōu)化:可以使用Intersection Observer API替代滾動(dòng)監(jiān)聽(tīng),更精準(zhǔn)地判斷元素是否進(jìn)入視口,避免頻繁計(jì)算滾動(dòng)距離帶來(lái)的性能損耗。
三、方案選型指南:不同場(chǎng)景怎么選?
四種方案沒(méi)有絕對(duì)的優(yōu)劣,關(guān)鍵是匹配業(yè)務(wù)場(chǎng)景,這里整理了一份選型對(duì)照表,幫你快速?zèng)Q策:
| 方案 | 核心優(yōu)勢(shì) | 核心劣勢(shì) | 適用場(chǎng)景 |
|---|---|---|---|
| 串行執(zhí)行 | 穩(wěn)定性高,無(wú)并發(fā)壓力 | 總耗時(shí)最長(zhǎng) | 請(qǐng)求有依賴(lài)、服務(wù)器抗壓弱 |
| Promise并行控制 | 效率與穩(wěn)定性平衡 | 需控制并發(fā)數(shù),邏輯稍復(fù)雜 | 無(wú)依賴(lài)批量請(qǐng)求、追求效率 |
| 列表分頁(yè) | 按需加載,邏輯簡(jiǎn)單 | 需用戶(hù)主動(dòng)切換頁(yè)碼 | 表格、分頁(yè)列表展示 |
| 內(nèi)容懶加載 | 首屏速度快,用戶(hù)體驗(yàn)好 | 需監(jiān)聽(tīng)滾動(dòng),適配復(fù)雜場(chǎng)景 | 無(wú)限滾動(dòng)、圖片密集頁(yè) |
四、終極建議:從根源減少請(qǐng)求次數(shù)
前面的方案都是“治標(biāo)”,最理想的方式是“治本”——從根源減少請(qǐng)求次數(shù)。這里分享兩個(gè)關(guān)鍵思路:
- 后端接口聚合:如果100次請(qǐng)求是獲取不同模塊的數(shù)據(jù)(如用戶(hù)信息、訂單信息、商品信息),可以協(xié)調(diào)后端開(kāi)發(fā)一個(gè)“聚合接口”,前端只需發(fā)起1次請(qǐng)求,后端內(nèi)部完成多數(shù)據(jù)的獲取和整合后返回。這種方式能從根本上解決請(qǐng)求過(guò)多問(wèn)題,效率最高;
- 數(shù)據(jù)緩存復(fù)用:對(duì)于不常變化的數(shù)據(jù)(如字典數(shù)據(jù)、分類(lèi)數(shù)據(jù)),可以用localStorage或sessionStorage緩存,首次請(qǐng)求后存入緩存,后續(xù)直接從緩存讀取,避免重復(fù)請(qǐng)求。
總結(jié)
AJAX請(qǐng)求過(guò)多的問(wèn)題,本質(zhì)是“資源請(qǐng)求與服務(wù)器/瀏覽器承載能力”的平衡問(wèn)題。我們?cè)趯?shí)際開(kāi)發(fā)中,應(yīng)優(yōu)先考慮“接口聚合+數(shù)據(jù)緩存”的治本方案;若無(wú)法實(shí)現(xiàn),則根據(jù)業(yè)務(wù)場(chǎng)景選擇串行、并行控制、分頁(yè)或懶加載的治標(biāo)方案。核心原則是:在保證系統(tǒng)穩(wěn)定性的前提下,最大限度提升用戶(hù)體驗(yàn)。希望本文的方案能幫你解決實(shí)際開(kāi)發(fā)中的痛點(diǎn),如果你有其他好的思路,歡迎在評(píng)論區(qū)交流!
以上就是AJAX請(qǐng)求次數(shù)過(guò)多的四種解決方案的詳細(xì)內(nèi)容,更多關(guān)于AJAX請(qǐng)求次數(shù)過(guò)多解決的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
解決ajax的delete、put方法接收不到參數(shù)的問(wèn)題方法
今天小編就為大家分享一篇解決ajax的delete、put方法接收不到參數(shù)的問(wèn)題方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-08-08
Ajax+php數(shù)據(jù)交互并且局部刷新頁(yè)面的實(shí)現(xiàn)詳解
這篇文章主要給大家介紹了關(guān)于利用Ajax與php數(shù)據(jù)交互并且局部刷新頁(yè)面的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編一起來(lái)學(xué)習(xí)學(xué)習(xí)吧。2017-07-07
淺析JSONP解決Ajax跨域訪(fǎng)問(wèn)問(wèn)題的思路詳解
JSONP是一種使用JSON數(shù)據(jù)的方式,返回的不是JSON對(duì)象,是包含JSON對(duì)象的javaScript腳本。接下來(lái)通過(guò)本文給大家介紹jsonp解決ajax跨域訪(fǎng)問(wèn)問(wèn)題的思路,非常不錯(cuò)具有參考借鑒價(jià)值,感興趣的朋友一起看下吧2016-05-05
關(guān)于Ajax技術(shù)原理的3點(diǎn)總結(jié)
這篇文章主要介紹了關(guān)于Ajax技術(shù)原理的3點(diǎn)總結(jié),需要的朋友可以參考下2014-12-12
服務(wù)端配置實(shí)現(xiàn)AJAX跨域請(qǐng)求
這篇文章主要介紹了服務(wù)端配置實(shí)現(xiàn)AJAX跨域請(qǐng)求的相關(guān)資料,需要的朋友可以參考下2015-02-02
Ajax遍歷jSon后對(duì)每一條數(shù)據(jù)進(jìn)行相應(yīng)的修改和刪除(代碼分享)
這篇文章主要介紹了Ajax遍歷jSon后對(duì)每一條數(shù)據(jù)進(jìn)行相應(yīng)的修改和刪除的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-11-11

