// Wiederholung mit exponentiellem Backoff und Jitter. Jeder Netzwerkaufruf // laeuft hierdurch - siehe docs/01-architecture.md, Abschnitt Fehlerverhalten. export type RetryOptions = { attempts?: number; baseMs?: number; maxMs?: number; onRetry?: (err: unknown, attempt: number, waitMs: number) => void; signal?: AbortSignal; }; export function sleep(ms: number, signal?: AbortSignal): Promise { return new Promise((resolve, reject) => { if (signal?.aborted) return reject(new Error('abgebrochen')); const t = setTimeout(resolve, ms); signal?.addEventListener('abort', () => { clearTimeout(t); reject(new Error('abgebrochen')); }, { once: true }); }); } export async function retry(fn: () => Promise, opts: RetryOptions = {}): Promise { const attempts = opts.attempts ?? 5; const base = opts.baseMs ?? 500; const max = opts.maxMs ?? 30_000; let lastErr: unknown; for (let i = 0; i < attempts; i++) { try { return await fn(); } catch (err) { lastErr = err; if (i === attempts - 1) break; const backoff = Math.min(max, base * 2 ** i); const wait = Math.round(backoff * (0.5 + Math.random() * 0.5)); opts.onRetry?.(err, i + 1, wait); await sleep(wait, opts.signal); } } throw lastErr; } /** * Haelt eine Verbindung dauerhaft am Leben: `connect` wird nach jedem Ende * neu aufgerufen, mit wachsender Pause. Der Zaehler wird zurueckgesetzt, * sobald eine Verbindung laenger als `stableMs` gehalten hat - sonst waechst * die Pause nach Tagen Laufzeit ins Absurde. */ export async function forever( connect: (signal: AbortSignal) => Promise, opts: { signal: AbortSignal; baseMs?: number; maxMs?: number; stableMs?: number; onWait?: (ms: number, failures: number) => void } ): Promise { const base = opts.baseMs ?? 1000; const max = opts.maxMs ?? 60_000; const stable = opts.stableMs ?? 60_000; let failures = 0; while (!opts.signal.aborted) { const started = Date.now(); try { await connect(opts.signal); } catch { /* Ursache hat der Aufrufer schon geloggt */ } if (opts.signal.aborted) return; if (Date.now() - started > stable) failures = 0; failures++; const wait = Math.min(max, base * 2 ** (failures - 1)); opts.onWait?.(wait, failures); try { await sleep(wait, opts.signal); } catch { return; } } }