如何在for循环中等待前一个fetch完成后再发起下一个请求?
解决异步Fetch循环中累加时间值不正确的问题
嘿,你的推测完全命中了问题的核心!问题就出在fetch是异步操作——你的for循环会同步地一口气发起所有请求,但每个请求的then回调要等请求完成后才会执行。这带来了两个关键问题:一是你用固定10秒的setTimeout来等所有请求完成太不可靠(万一某个请求10秒后还没返回,那累加就不完整);二是回调的执行顺序不一定和循环顺序一致,最终导致你的time变量只拿到了最后一个请求的结果(或者某个请求的结果覆盖了之前的)。
下面给你两种靠谱的解决方案,根据你的需求选就行:
方案一:按顺序执行请求(适合有依赖的场景)
如果你的请求之间有先后依赖(比如下一个请求需要用上一个的结果),或者你希望严格按数组顺序累加,那就用async/await把循环改成异步的,每次等当前请求完成后再进行下一次。这样不仅能保证累加顺序正确,还能彻底丢掉不靠谱的setTimeout。
修改后的代码:
// 把逻辑包在异步函数里 async function calculateTotalJourneyTime() { let totalTime = 0; // 用let声明,避免变量提升的坑 // 这里替换成你实际的变量值 const seconds = /* 你的秒数 */; const temp = /* 温度值 */; const dayNum = /* 星期几的数值 */; for (let i = 0; i < journeyArr.length; i++){ // 用let代替var,避免循环变量泄漏 console.log("当前循环开始时的总时间: ", totalTime); const currentJourney = journeyArr[i]; // 提取当前行程的参数 const startStop = currentJourney.stops[0].id; const endStop = currentJourney.stops.at(-1).id; // 用at(-1)取最后一项更简洁 const { routeid, lineid } = currentJourney; // 对象解构,代码更清爽 // 等待当前fetch请求完成 const response = await fetch(`http://localhost:8000/routes/api/predict/?lineid=${lineid}&start_stop=${startStop}&end_stop=${endStop}&routeid=${routeid}&time_secs=${seconds}&temp=${temp}&rain=0.16&dow=${dayNum}`); const data = await response.json(); // 累加时间 totalTime += data.journey_info.journey_time.hours * 60; totalTime += data.journey_info.journey_time.minutes; } // 所有请求完成后再计算并显示结果 const hours = Math.floor(totalTime / 60); const minutes = totalTime % 60; timeLeft.innerHTML = `${hours} hour(s) ${minutes} minutes`; } // 调用函数启动计算 calculateTotalJourneyTime();
这个方案逻辑直观,顺序执行每个请求,缺点是总耗时等于所有请求耗时的总和,适合请求数量不多或者有依赖的场景。
方案二:并行发起所有请求(效率更高)
如果你的请求之间没有依赖关系,强烈推荐这个方案!所有请求同时发起,总耗时等于最慢的那个请求的时间,效率提升明显。我们用Promise.all来收集所有请求的结果,然后统一累加。
function calculateTotalJourneyTime() { // 这里替换成你实际的变量值 const seconds = /* 你的秒数 */; const temp = /* 温度值 */; const dayNum = /* 星期几的数值 */; // 生成所有fetch请求的Promise数组,每个Promise返回当前行程的时间值 const journeyTimePromises = journeyArr.map(journey => { const startStop = journey.stops[0].id; const endStop = journey.stops.at(-1).id; const { routeid, lineid } = journey; return fetch(`http://localhost:8000/routes/api/predict/?lineid=${lineid}&start_stop=${startStop}&end_stop=${endStop}&routeid=${routeid}&time_secs=${seconds}&temp=${temp}&rain=0.16&dow=${dayNum}`) .then(response => { // 处理HTTP错误,比如404、500 if (!response.ok) throw new Error(`请求失败: ${response.status}`); return response.json(); }) .then(data => { // 计算当前行程的总分钟数并返回 return data.journey_info.journey_time.hours * 60 + data.journey_info.journey_time.minutes; }); }); // 等待所有请求完成 Promise.all(journeyTimePromises) .then(allJourneyTimes => { // 累加所有时间 const totalTime = allJourneyTimes.reduce((sum, time) => sum + time, 0); // 计算并显示结果 const hours = Math.floor(totalTime / 60); const minutes = totalTime % 60; timeLeft.innerHTML = `${hours} hour(s) ${minutes} minutes`; }) .catch(error => { // 处理请求失败的情况,比如网络错误、HTTP错误 console.error("计算总时间时出错:", error); timeLeft.innerHTML = "无法计算行程时间,请稍后重试"; }); } // 调用函数启动计算 calculateTotalJourneyTime();
这个方案效率更高,但要注意:如果任何一个请求失败,Promise.all会直接进入catch块。如果想允许部分请求失败,你可以改用Promise.allSettled,然后在结果中过滤掉失败的请求。
几个额外的小建议
- 把
var换成let或const,避免变量提升导致的意外问题,这是JavaScript开发的好习惯。 - 永远不要用固定时长的
setTimeout等待异步操作完成,这完全不可靠——网络状况是不可控的,用Promise的方法(await或Promise.all)才是正确的姿势。 - 一定要添加错误处理,比如在fetch中检查
response.ok,在Promise.all中添加catch,这样用户在请求失败时能得到友好提示,你也能快速定位问题。
内容的提问来源于stack exchange,提问作者Ronan Byrne
相关产品推荐
相关产品推荐

