使用JS循环调用API触发429错误,求有效延迟请求方案
解决TomTom地理编码API返回429请求超限的问题
你现在遇到的问题是循环里批量发起的fetch请求几乎同时发送,直接触发了TomTom的请求频率限制。之前用setTimeout没效果,是因为循环会一次性创建所有定时器,到点后请求还是扎堆发,根本没错开时间。
下面给你两种可行的解决方案:
方案一:串行请求+固定间隔(最稳妥)
用async/await让每个请求完成后再发下一个,同时给每个请求加固定延迟,严格控制请求频率,符合API的限制要求。
修改后的完整代码
let koordinatlar = []; const API_KEY = '你的API密钥'; // 替换成你实际的API Key // 封装带延迟和重试的地理编码请求函数 async function getGeocode(address) { // 先等1秒(TomTom免费版通常限制1次/秒,可根据你的套餐调整) await new Promise(resolve => setTimeout(resolve, 1000)); const encodedAddr = encodeURIComponent(address); const response = await fetch(`https://api.tomtom.com/search/2/geocode/${encodedAddr}.json?key=${API_KEY}`); // 遇到429就多等会儿再重试 if (response.status === 429) { console.log('触发限流,2秒后重试'); await new Promise(resolve => setTimeout(resolve, 2000)); return getGeocode(address); } if (!response.ok) { throw new Error(`请求失败:${response.status}`); } return await response.json(); } // 批量处理地址的主函数 async function processAllAddresses() { // 先把要处理的10条地址整理出来 const targetAddresses = yesilListe[0].slice(0, 10).map(item => `${item[5]} ${item[6]} ${item[7]} ${item[8]}` ); for (let i = 0; i < targetAddresses.length; i++) { const currentAddr = targetAddresses[i]; const currentItem = yesilListe[0][i]; try { const geocodeData = await getGeocode(currentAddr); // 把结果存入数组 koordinatlar.push([ geocodeData.results[0].position.lon, geocodeData.results[0].position.lat, `${currentItem[2]} ${currentItem[3]}`, currentItem[4], currentAddr ]); } catch (err) { console.log(`处理第${i+1}条地址出错:`, err); } } // 所有请求完成后再打印结果,原代码里循环结束就打印会拿到空数组 console.log(koordinatlar); } // 启动处理 processAllAddresses();
方案二:分批次并行请求(效率更高)
如果你的API套餐允许少量并发(比如5次/秒),可以分批次发送请求,每批次处理N个请求,批次之间加延迟,平衡效率和限流规则。
示例代码
let koordinatlar = []; const API_KEY = '你的API密钥'; const BATCH_SIZE = 5; // 每批处理5个请求 const BATCH_DELAY = 1000; // 批次间隔1秒 async function getGeocode(address) { const encodedAddr = encodeURIComponent(address); const response = await fetch(`https://api.tomtom.com/search/2/geocode/${encodedAddr}.json?key=${API_KEY}`); if (response.status === 429) { console.log('触发限流,2秒后重试'); await new Promise(resolve => setTimeout(resolve, 2000)); return getGeocode(address); } if (!response.ok) { throw new Error(`请求失败:${response.status}`); } return await response.json(); } async function processInBatches() { const targetItems = yesilListe[0].slice(0, 10); for (let i = 0; i < targetItems.length; i += BATCH_SIZE) { // 取出当前批次的条目 const batchItems = targetItems.slice(i, i + BATCH_SIZE); // 并行处理当前批次的所有请求 const batchPromises = batchItems.map(async item => { const address = `${item[5]} ${item[6]} ${item[7]} ${item[8]}`; try { const data = await getGeocode(address); return [ data.results[0].position.lon, data.results[0].position.lat, `${item[2]} ${item[3]}`, item[4], address ]; } catch (err) { console.log(`处理地址出错:`, err); return null; } }); // 等待当前批次所有请求完成 const batchResults = await Promise.all(batchPromises); // 过滤掉错误的结果,存入数组 koordinatlar.push(...batchResults.filter(res => res !== null)); // 如果不是最后一批,就等一会儿再发下一批 if (i + BATCH_SIZE < targetItems.length) { await new Promise(resolve => setTimeout(resolve, BATCH_DELAY)); } } console.log(koordinatlar); } processInBatches();
额外注意点
- 原代码中
console.log(koordinatlar)的位置不对,因为fetch是异步操作,循环结束时所有请求都还没完成,所以会打印空数组,上面的代码已经把打印移到所有请求完成后。 - 一定要根据TomTom API的具体限制调整延迟时间,比如付费套餐可能允许更高的请求频率,延迟可以设短一点。
- 增加了429错误的重试逻辑,避免偶尔的限流导致请求失败。
内容的提问来源于stack exchange,提问作者Fuber
相关产品推荐
相关产品推荐

