import { AppError, URLInputErrorType } from './errors'; // リトライ可能なエラーかどうか判定 export function isRetryableError(error: unknown): boolean { if (error instanceof AppError) { return [ URLInputErrorType.NETWORK_ERROR, URLInputErrorType.TIMEOUT, ].includes(error.code as URLInputErrorType); } return false; } // エラーの詳細情報を取得 export function getErrorDetails(error: unknown): { message: string; code?: string; statusCode?: number; isRetryable: boolean; } { if (error instanceof AppError) { return { message: error.message, code: error.code, statusCode: error.statusCode, isRetryable: isRetryableError(error), }; } if (error instanceof Error) { return { message: error.message, isRetryable: false, }; } return { message: '予期しないエラーが発生しました', isRetryable: false, }; } // 指数バックオフでリトライ export async function retryWithBackoff( fn: () => Promise, maxRetries: number = 3, initialDelay: number = 1000, ): Promise { let lastError: unknown; for (let i = 0; i < maxRetries; i++) { try { return await fn(); } catch (error) { lastError = error; // リトライ不可能なエラーの場合は即座に失敗 if (!isRetryableError(error)) { throw error; } // 最後の試行の場合はエラーを投げる if (i === maxRetries - 1) { throw error; } // 指数バックオフで待機 const delay = initialDelay * Math.pow(2, i); await new Promise((resolve) => setTimeout(resolve, delay)); } } throw lastError; }