fix: Job-Polling statt Langläufer-Request gegen "Failed to fetch"
POST /api/process hielt die HTTP-Verbindung 30–120s+ offen, während
claude -p lief. Jeder Reverse-Proxy (und kurze Netz-Hänger) kappt so
eine Verbindung, der Browser sieht nur "Failed to fetch" — ununterscheidbar
von einem echten Claude-Fehler.
- Server: POST registriert einen Job und antwortet sofort mit 202 {jobId};
claude läuft im Hintergrund, Ergebnis/Fehler landen im Job-Store
(TTL 15min, periodische Bereinigung). Neuer GET /api/process/:jobId
liefert pending/done/error in kurzen, proxy-sicheren Requests.
- Frontend: pollt den Job alle 2s; ein transienter Netzfehler beim Pollen
wird erneut versucht statt die ganze Analyse abzubrechen. Echte
Claude-Fehler werden jetzt mit Klartext angezeigt statt "Failed to fetch".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P7fRh8N5kQsicT7q4gSnua
This commit is contained in:
parent
3c669fb003
commit
a3b36d5c74
2 changed files with 161 additions and 23 deletions
|
|
@ -33,16 +33,85 @@ export async function processDocuments(
|
|||
});
|
||||
|
||||
if (!res.ok) {
|
||||
let message = `Server antwortete mit ${res.status}`;
|
||||
try {
|
||||
const data = await res.json();
|
||||
if (data?.error) message = data.error;
|
||||
if (data?.details) message += ` — ${data.details}`;
|
||||
} catch {
|
||||
// fall through
|
||||
}
|
||||
throw new Error(message);
|
||||
throw new Error(await errorMessageFrom(res));
|
||||
}
|
||||
|
||||
return (await res.json()) as FormResponse;
|
||||
const { jobId } = (await res.json()) as { jobId?: string };
|
||||
if (!jobId) throw new Error('Server hat keine Job-ID zurückgegeben.');
|
||||
|
||||
return pollJob(jobId);
|
||||
}
|
||||
|
||||
const POLL_INTERVAL_MS = 2000;
|
||||
const MAX_WAIT_MS = 6 * 60 * 1000;
|
||||
const MAX_CONSECUTIVE_NETWORK_ERRORS = 8;
|
||||
|
||||
// Pollt den Job-Status. Kurze Requests -> ein Proxy-Timeout oder ein kurzer
|
||||
// Netz-Hänger killt nicht mehr die ganze Analyse, sondern wird einfach beim
|
||||
// nächsten Tick erneut versucht.
|
||||
async function pollJob(jobId: string): Promise<FormResponse> {
|
||||
const start = Date.now();
|
||||
let networkErrors = 0;
|
||||
|
||||
while (true) {
|
||||
if (Date.now() - start > MAX_WAIT_MS) {
|
||||
throw new Error('Zeitüberschreitung beim Warten auf die Analyse.');
|
||||
}
|
||||
await delay(POLL_INTERVAL_MS);
|
||||
|
||||
let res: Response;
|
||||
try {
|
||||
res = await fetch(`/api/process/${jobId}`);
|
||||
networkErrors = 0;
|
||||
} catch {
|
||||
// "Failed to fetch" beim Pollen = transient -> weiter versuchen.
|
||||
if (++networkErrors >= MAX_CONSECUTIVE_NETWORK_ERRORS) {
|
||||
throw new Error(
|
||||
'Verbindung zum Server verloren. Läuft der Backend-Container noch?'
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (res.status === 404) {
|
||||
throw new Error(
|
||||
'Analyse-Job nicht mehr vorhanden (Server neugestartet?). Bitte erneut versuchen.'
|
||||
);
|
||||
}
|
||||
if (!res.ok) {
|
||||
throw new Error(await errorMessageFrom(res));
|
||||
}
|
||||
|
||||
const data = (await res.json()) as {
|
||||
status?: 'pending' | 'done' | 'error';
|
||||
result?: FormResponse;
|
||||
error?: string;
|
||||
details?: string;
|
||||
};
|
||||
|
||||
if (data.status === 'pending') continue;
|
||||
if (data.status === 'done' && data.result) return data.result;
|
||||
if (data.status === 'error') {
|
||||
let message = data.error ?? 'Claude CLI failed';
|
||||
if (data.details) message += ` — ${data.details}`;
|
||||
throw new Error(message);
|
||||
}
|
||||
throw new Error('Unerwartete Antwort vom Server.');
|
||||
}
|
||||
}
|
||||
|
||||
function delay(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async function errorMessageFrom(res: Response): Promise<string> {
|
||||
let message = `Server antwortete mit ${res.status}`;
|
||||
try {
|
||||
const data = await res.json();
|
||||
if (data?.error) message = data.error;
|
||||
if (data?.details) message += ` — ${data.details}`;
|
||||
} catch {
|
||||
// fall through
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue