Timeoutパターン
Timeoutパターン
Section titled “Timeoutパターン”タイムアウトパターンを詳しく解説します。
なぜTimeoutが必要なのか
Section titled “なぜTimeoutが必要なのか”無限待機の問題
Section titled “無限待機の問題”問題のある実装:
// ❌ 悪い例: タイムアウトがないclass PaymentService { async chargePayment(orderId: string, amount: number): Promise<PaymentResult> { // タイムアウトがない場合、無限に待機する可能性がある const response = await fetch('https://payment-api.example.com/charge', { method: 'POST', body: JSON.stringify({ orderId, amount }), });
return await response.json(); }}問題点:
- リソースの浪費: 長時間待機するリクエストがリソースを占有
- 可用性の低下: スレッドプールが枯渇する可能性
- ユーザー体験の悪化: ユーザーが長時間待機する
影響:
- リソースの浪費
- 可用性の低下
- ユーザー体験の悪化
Timeoutによる解決
Section titled “Timeoutによる解決”改善された実装:
// ✅ 良い例: Timeoutパターンを使用class TimeoutPolicy { async execute<T>(fn: () => Promise<T>, timeoutMs: number): Promise<T> { return Promise.race([ fn(), new Promise<T>((_, reject) => { setTimeout(() => { reject(new TimeoutError(`Operation timed out after ${timeoutMs}ms`)); }, timeoutMs); }), ]); }}
class PaymentService { private timeoutPolicy: TimeoutPolicy;
constructor() { this.timeoutPolicy = new TimeoutPolicy(); }
async chargePayment(orderId: string, amount: number): Promise<PaymentResult> { return await this.timeoutPolicy.execute(async () => { const response = await fetch('https://payment-api.example.com/charge', { method: 'POST', body: JSON.stringify({ orderId, amount }), });
if (!response.ok) { throw new Error('Payment failed'); }
return await response.json(); }, 5000); // 5秒でタイムアウト }}メリット:
- リソースの節約: 長時間待機するリクエストを中断
- 可用性の向上: リソースを解放して他のリクエストを処理
- ユーザー体験の向上: 適切なタイムアウトでユーザーにフィードバック
階層的タイムアウト
Section titled “階層的タイムアウト”タイムアウトの階層
Section titled “タイムアウトの階層”実装例:
class HierarchicalTimeout { async execute<T>( fn: () => Promise<T>, timeouts: { operation: number; total: number } ): Promise<T> { const startTime = Date.now();
return Promise.race([ this.executeWithOperationTimeout(fn, timeouts.operation), new Promise<T>((_, reject) => { setTimeout(() => { reject(new TimeoutError(`Total timeout after ${timeouts.total}ms`)); }, timeouts.total); }), ]); }
private async executeWithOperationTimeout<T>( fn: () => Promise<T>, timeoutMs: number ): Promise<T> { return Promise.race([ fn(), new Promise<T>((_, reject) => { setTimeout(() => { reject(new TimeoutError(`Operation timeout after ${timeoutMs}ms`)); }, timeoutMs); }), ]); }}
// 使用例const timeout = new HierarchicalTimeout();
await timeout.execute( async () => { // 複数の操作を実行 await paymentService.chargePayment(orderId, amount); await inventoryService.reserveInventory(orderId, items); await notificationService.sendConfirmation(userId); }, { operation: 3000, // 各操作は3秒でタイムアウト total: 10000, // 全体は10秒でタイムアウト });サービスごとのタイムアウト設定
Section titled “サービスごとのタイムアウト設定”推奨タイムアウト値
Section titled “推奨タイムアウト値”設定例:
interface ServiceTimeouts { payment: number; // 5秒 inventory: number; // 3秒 notification: number; // 2秒 search: number; // 1秒}
const timeouts: ServiceTimeouts = { payment: 5000, // 決済は重要なので長め inventory: 3000, // 在庫は中程度 notification: 2000, // 通知は短めでOK search: 1000, // 検索は短め};
class ServiceClient { async callService<T>( serviceName: keyof ServiceTimeouts, fn: () => Promise<T> ): Promise<T> { const timeout = timeouts[serviceName];
return Promise.race([ fn(), new Promise<T>((_, reject) => { setTimeout(() => { reject(new TimeoutError(`${serviceName} timeout after ${timeout}ms`)); }, timeout); }), ]); }}実践的な実装例
Section titled “実践的な実装例”AbortControllerを使用した実装
Section titled “AbortControllerを使用した実装”class TimeoutPolicy { async execute<T>(fn: () => Promise<T>, timeoutMs: number): Promise<T> { const controller = new AbortController(); const timeoutId = setTimeout(() => { controller.abort(); }, timeoutMs);
try { const result = await fn(); clearTimeout(timeoutId); return result; } catch (error) { clearTimeout(timeoutId);
if (controller.signal.aborted) { throw new TimeoutError(`Operation timed out after ${timeoutMs}ms`); }
throw error; } }}
class PaymentService { async chargePayment(orderId: string, amount: number): Promise<PaymentResult> { const controller = new AbortController(); const timeoutId = setTimeout(() => { controller.abort(); }, 5000);
try { const response = await fetch('https://payment-api.example.com/charge', { method: 'POST', body: JSON.stringify({ orderId, amount }), signal: controller.signal, });
clearTimeout(timeoutId);
if (!response.ok) { throw new Error('Payment failed'); }
return await response.json(); } catch (error) { clearTimeout(timeoutId);
if (error.name === 'AbortError') { throw new TimeoutError('Payment request timed out'); }
throw error; } }}Timeoutパターンのポイント:
- なぜ必要か: リソースの浪費を防ぎ、可用性を向上
- 階層的タイムアウト: 操作レベルと全体レベルのタイムアウト
- サービスごとの設定: サービスごとに適切なタイムアウト値を設定
- 実装: AbortControllerを使用してリクエストを中断
適切なTimeoutパターンの実装により、リソースを効率的に使用できるシステムを構築できます。