Google Apps Script中带计数器与延迟的API限流请求实现方法
问题解答
1. Lock类的适配性说明
Lock类适用于多用户/多实例同时执行同一段Apps Script代码时的资源抢占冲突场景,你当前是单脚本串行调用API,不存在多并发资源竞争问题,不需要用Lock类。你之前代码未达预期是逻辑错误导致,和异步性无关。
2. 现有限流思路的问题
你的基础限流思路方向是对的,但代码存在几个核心逻辑错误:
- 循环条件使用了或运算符
||,只要三个条件任意一个满足就继续执行,会直接突破限流阈值 - 秒级计数满后错误重置了上限值
rateLimitSec,而非已用计数rateLimitSecUsed - 递归调用
example()会导致循环嵌套,计数器状态混乱,无法正常遍历后续数据 - 仅做了秒级的延迟处理,没有适配分钟级限流的等待逻辑
3. 最优实现方案
采用固定窗口计数法实现限流,逻辑简单易维护,完全适配你的需求,代码示例如下:
var app = SpreadsheetApp; var sheet = app.getActiveSpreadsheet().getSheetByName("data"); var maxRows = sheet.getLastRow(); var data = sheet.getRange(2, 1, maxRows - 1, sheet.getLastColumn()).getValues(); // 限流配置 const RATE_LIMIT_SEC = 50; // 每秒上限 const RATE_LIMIT_MIN = 2500; // 每分钟上限 // 计数器与窗口起始时间 let secUsed = 0; let minUsed = 0; let secWindowStart = Date.now(); let minWindowStart = Date.now(); function queryAllData() { for (let i = 0; i < data.length; i++) { // 先更新时间窗口,若进入新窗口则重置对应计数器 const now = Date.now(); // 秒级窗口检查:1秒=1000毫秒 if (now - secWindowStart >= 1000) { secUsed = 0; secWindowStart = now; } // 分钟级窗口检查:1分钟=60000毫秒 if (now - minWindowStart >= 60000) { minUsed = 0; minWindowStart = now; } // 先判断是否触达分钟级限流,是则等待到下一分钟 if (minUsed >= RATE_LIMIT_MIN) { const waitTime = 60000 - (now - minWindowStart); Utilities.sleep(waitTime); // 等待结束后重置计数 minUsed = 0; minWindowStart = Date.now(); secUsed = 0; secWindowStart = Date.now(); } // 再判断是否触达秒级限流,是则等待到下一秒 if (secUsed >= RATE_LIMIT_SEC) { const waitTime = 1000 - (now - secWindowStart); Utilities.sleep(waitTime); // 等待结束后重置计数 secUsed = 0; secWindowStart = Date.now(); } // 替换为你自己的API请求逻辑 console.log(`正在处理第${i+1}条数据,当前秒级已用:${secUsed+1},分钟级已用:${minUsed+1}`); // 计数+1 secUsed++; minUsed++; } }
4. 逻辑说明
- 全程单循环串行执行,没有递归嵌套,状态清晰不易出错
- 每次执行请求前先检查时间窗口,自动重置过期的计数
- 触达限流阈值时会计算精准的等待时间,而非固定等1秒/1分钟,尽可能提升执行效率
- 优先检查分钟级限流,避免出现秒级符合要求但分钟级超限的问题
内容的提问来源于stack exchange,提问作者user1582208
相关产品推荐
相关产品推荐

