MCPcopy Create free account
hub / github.com/scriptscat/scriptcat / RateLimiter

Class RateLimiter

packages/filesystem/limiter.ts:10–83  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

8 * 控制并发操作数量,防止过多并发请求
9 */
10export class RateLimiter {
11 private queue: Array<() => void> = [];
12
13 private running = 0;
14
15 private maxConcurrent: number;
16
17 constructor(maxConcurrent = 5) {
18 this.maxConcurrent = maxConcurrent;
19 }
20
21 /**
22 * 执行限速操作
23 * @param fn 要执行的操作函数
24 * @param op 操作类型,用于在遇到 429 时判断是否允许自动重试。默认值 "unknown" 不在白名单内,会被视为不可重试
25 * @returns 操作结果
26 */
27 async execute<T>(fn: () => Promise<T>, op = "unknown"): Promise<T> {
28 // 如果当前运行的操作数已达到上限,则等待
29 while (this.running >= this.maxConcurrent) {
30 await new Promise<void>((resolve) => {
31 this.queue.push(resolve);
32 });
33 }
34
35 this.running++;
36 try {
37 return await this.executeWithRetry(fn, op);
38 } finally {
39 this.running--;
40 // 执行完成后,从队列中取出下一个等待的操作
41 const next = this.queue.shift();
42 if (next) {
43 next();
44 }
45 }
46 }
47
48 /**
49 * 执行操作并处理 429 错误重试
50 * @param fn 要执行的操作函数
51 * @param op 操作类型,用于判定该操作在遇到 429 时是否进入指数退避重试
52 * @returns 操作结果
53 */
54 private async executeWithRetry<T>(fn: () => Promise<T>, op: string): Promise<T> {
55 // 最多重试 10 次
56 for (let i = 0; i <= 10; i++) {
57 try {
58 return await fn();
59 } catch (error) {
60 // 检查错误字符串中是否包含 429
61 const errorStr = String(error).toLowerCase();
62 if (this.shouldRetry429(op, ` ${errorStr} `) && i < 10) {
63 // 遇到 429 错误且未达到重试上限,采用指数退避策略延迟后继续重试
64 const delay = Math.min(2000 * Math.pow(2, i), 60000);
65 await new Promise((resolve) => setTimeout(resolve, delay));
66 // 继续下一次循环重试
67 continue;

Callers

nothing calls this directly

Calls

no outgoing calls

Tested by

no test coverage detected