Skip to main content
Glama
retry.ts3.96 kB
export interface RetryOptions { retries: number; factor: number; minTimeout: number; maxTimeout: number; randomize?: boolean; onRetry?: (error: Error, attempt: number) => void; } export class RetryError extends Error { public readonly attempts: number; public readonly lastError: Error; constructor(message: string, attempts: number, lastError: Error) { super(message); this.name = 'RetryError'; this.attempts = attempts; this.lastError = lastError; } } export async function retry<T>( fn: () => Promise<T>, options: RetryOptions ): Promise<T> { const { retries, factor, minTimeout, maxTimeout, randomize = false, onRetry } = options; let lastError: Error; for (let attempt = 0; attempt <= retries; attempt++) { try { return await fn(); } catch (error) { lastError = error as Error; // 如果是最後一次嘗試,直接拋出錯誤 if (attempt === retries) { throw new RetryError( `操作失敗,已重試 ${retries} 次: ${lastError.message}`, attempt + 1, lastError ); } // 調用重試回調 if (onRetry) { onRetry(lastError, attempt + 1); } // 計算延遲時間 let delay = Math.min(minTimeout * Math.pow(factor, attempt), maxTimeout); if (randomize) { delay = delay * (Math.random() + 0.5); // 0.5x 到 1.5x 的隨機化 } // 等待延遲 await new Promise(resolve => setTimeout(resolve, delay)); } } // 這行不應該被執行到,但為了 TypeScript 的類型檢查 throw lastError!; } // 裝飾器版本的重試功能 export function retryMethod(options: RetryOptions) { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = async function (...args: any[]) { return retry(() => originalMethod.apply(this, args), options); }; return descriptor; }; } // 特定錯誤類型的重試 export async function retryOnError<T>( fn: () => Promise<T>, options: RetryOptions, retryableErrors: (string | RegExp)[] ): Promise<T> { const isRetryableError = (error: Error): boolean => { return retryableErrors.some(pattern => { if (typeof pattern === 'string') { return error.message.includes(pattern) || error.name === pattern; } else { return pattern.test(error.message) || pattern.test(error.name); } }); }; let lastError: Error; for (let attempt = 0; attempt <= options.retries; attempt++) { try { return await fn(); } catch (error) { lastError = error as Error; // 如果不是可重試的錯誤,直接拋出 if (!isRetryableError(lastError)) { throw lastError; } // 如果是最後一次嘗試,拋出重試錯誤 if (attempt === options.retries) { throw new RetryError( `操作失敗,已重試 ${options.retries} 次: ${lastError.message}`, attempt + 1, lastError ); } // 調用重試回調 if (options.onRetry) { options.onRetry(lastError, attempt + 1); } // 計算延遲時間 let delay = Math.min( options.minTimeout * Math.pow(options.factor, attempt), options.maxTimeout ); if (options.randomize) { delay = delay * (Math.random() + 0.5); } await new Promise(resolve => setTimeout(resolve, delay)); } } throw lastError!; } // 指數退避重試(常用於網路請求) export async function exponentialBackoff<T>( fn: () => Promise<T>, maxRetries: number = 3, baseDelay: number = 1000 ): Promise<T> { return retry(fn, { retries: maxRetries, factor: 2, minTimeout: baseDelay, maxTimeout: 30000, randomize: true }); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/SunZhi-Will/website-to-markdown-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server