异步Fetch中setTimeout失效问题求助(JavaScript)
批量请求16k数据时的浏览器阻塞问题及解决方法
问题描述
需要获取约16k个对象的完整数据,仅获取ID时正常,但按ID逐个请求完整数据时Chrome无法处理。尝试添加Promise报错,添加常规1秒setTimeout后,程序会先等待1秒再一次性发起所有请求,而非每条间隔1秒。相关代码如下,且setTimeout仅能放在指定位置:
async function fetchMODEL(endpoint) { var array; const whatever = await fetch("https://link.com/" + endpoint) .then((response) => response.json()) .then((data) => (array = data)); return whatever; } function detectIDs(){ fetchMODEL("cars") .then((response) => { var data = response; var allIds = data.map(item => {return item.id;}) //console.log(JSON.stringify(allIds)) //return allIds; var illus = []; for(i=0; i < allIds.length ;i++){ //setTimeout( fetchMODEL("cars/" + allIds[i]) .then((response) => { //console.log(typeof response.illustrator) var artistData = {}; if(typeof response.illustrator == "undefined"){ artistData.edit="EDIT"; artistData.name = response.name; artistData.id = response.id; illus.push(artistData); console.log(artistData) }else{ artistData.id = response.id; artistData.illustrator = response.illustrator; artistData.name = response.name; illus.push(artistData); console.log(artistData); } })//,100) } }) } var orgSetIDs = detectIDs();
问题原因
- 同步循环瞬间发起所有请求:原代码的
for循环是同步执行的,会在极短时间内遍历完16k个ID并发起所有fetch请求。Chrome对同一域名的并发HTTP连接数有限制(通常为6个),大量请求排队会导致浏览器阻塞,甚至触发服务器的限流机制。 - setTimeout未实现间隔效果:直接在循环中添加固定延迟的
setTimeout时,循环会瞬间将所有setTimeout任务加入事件队列,且所有任务的延迟时间相同,因此会在延迟结束后一次性触发所有请求,无法实现逐个间隔的效果。此外,原代码中用var声明的i是函数级作用域,所有setTimeout回调会共享同一个i值,最终都会使用循环结束后的i值(即16k),导致请求的ID错误。
解决方案
方案1:逐个请求+固定间隔(最稳妥)
改用async/await配合for...of循环,确保每个请求完成并等待指定间隔后再发起下一个请求,避免并发数过高:
// 简化fetchMODEL函数,统一用async/await风格 async function fetchMODEL(endpoint) { const response = await fetch(`https://link.com/${endpoint}`); if (!response.ok) { throw new Error(`请求失败: ${response.status}`); } return response.json(); } async function detectIDs() { const data = await fetchMODEL("cars"); const allIds = data.map(item => item.id); const illus = []; for (const id of allIds) { // 每个请求前等待1秒 await new Promise(resolve => setTimeout(resolve, 1000)); try { const response = await fetchMODEL(`cars/${id}`); const artistData = { id, name: response.name }; if (typeof response.illustrator === "undefined") { artistData.edit = "EDIT"; } else { artistData.illustrator = response.illustrator; } illus.push(artistData); console.log(artistData); } catch (err) { console.error(`请求ID ${id} 失败:`, err); } } return illus; } detectIDs();
方案2:批量并发请求(效率更高)
如果需要更快的速度,可以采用批量并发的方式,每次同时发起少量请求(比如10个),等待批次完成后再发起下一批,既控制并发数,又提高效率:
async function fetchMODEL(endpoint) { const response = await fetch(`https://link.com/${endpoint}`); if (!response.ok) { throw new Error(`请求失败: ${response.status}`); } return response.json(); } async function detectIDs() { const data = await fetchMODEL("cars"); const allIds = data.map(item => item.id); const illus = []; const batchSize = 10; // 每批并发10个请求 for (let i = 0; i < allIds.length; i += batchSize) { const batchIds = allIds.slice(i, i + batchSize); // 发起当前批次的所有请求 const batchPromises = batchIds.map(async id => { try { const response = await fetchMODEL(`cars/${id}`); const artistData = { id, name: response.name }; if (typeof response.illustrator === "undefined") { artistData.edit = "EDIT"; } else { artistData.illustrator = response.illustrator; } return artistData; } catch (err) { console.error(`请求ID ${id} 失败:`, err); return null; } }); // 等待当前批次所有请求完成 const batchResults = await Promise.all(batchPromises); // 过滤失败的请求结果并加入数组 illus.push(...batchResults.filter(item => item !== null)); // 批次之间间隔1秒(可选,根据服务器压力调整) await new Promise(resolve => setTimeout(resolve, 1000)); } return illus; } detectIDs();
方案3:修复原setTimeout的间隔问题(不推荐)
如果必须使用原位置的setTimeout,可以通过let声明i(块级作用域)来确保每个setTimeout获取正确的ID,并设置递增的延迟时间:
async function fetchMODEL(endpoint) { var array; const whatever = await fetch("https://link.com/" + endpoint) .then((response) => response.json()) .then((data) => (array = data)); return whatever; } function detectIDs(){ fetchMODEL("cars") .then((response) => { var data = response; var allIds = data.map(item => item.id); var illus = []; // 用let声明i,确保每个循环块的i独立 for(let i=0; i < allIds.length ;i++){ setTimeout(() => { fetchMODEL("cars/" + allIds[i]) .then((response) => { var artistData = {}; if(typeof response.illustrator == "undefined"){ artistData.edit="EDIT"; artistData.name = response.name; artistData.id = response.id; illus.push(artistData); console.log(artistData) }else{ artistData.id = response.id; artistData.illustrator = response.illustrator; artistData.name = response.name; illus.push(artistData); console.log(artistData); } }) }, i*1000) // 每个请求延迟i*1000ms,实现间隔1秒 } }) } detectIDs();
注意:此方案会在瞬间创建16k个
setTimeout任务,虽然能实现间隔请求,但浏览器事件队列会积压大量任务,仍可能影响性能,因此优先推荐方案1或方案2。
内容的提问来源于stack exchange,提问作者TCGPlaza
相关产品推荐
相关产品推荐

