fix(nodejs-client): fix timeout handling in retry loop

Fix critical bug in retry loop timeout control:
- Move AbortController creation inside while loop to ensure each retry uses a fresh controller
- Previous implementation caused retries to use an already-aborted controller, making retries fail immediately
- Add eslint-disable comments to explain necessary type assertions
- Improve code comments explaining DOM ReadableStream type conversion

This fix ensures the retry mechanism works correctly with independent timeout control for each attempt.
This commit is contained in:
yyh 2026-01-05 14:10:05 +08:00
parent df67842fae
commit 249a491743
No known key found for this signature in database

View File

@ -329,14 +329,15 @@ export class HttpClient {
}
}
const abortController = new AbortController();
const timeoutId = setTimeout(() => abortController.abort(), timeout * 1000);
let attempt = 0;
// `attempt` is a zero-based retry counter
// Total attempts = 1 (initial) + maxRetries
// e.g., maxRetries=3 means: attempt 0 (initial), then retries at 1, 2, 3
while (true) {
// Create a new AbortController for each attempt to handle timeouts properly
const abortController = new AbortController();
const timeoutId = setTimeout(() => abortController.abort(), timeout * 1000);
try {
const response = await fetch(url, {
method,
@ -373,6 +374,9 @@ export class HttpClient {
// For Node.js, we need to convert web streams to Node.js streams
if (response.body) {
const { Readable } = await import("node:stream");
// Type assertion needed: DOM ReadableStream vs Node.js stream types are incompatible
// but Readable.fromWeb handles the conversion correctly at runtime
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
responseData = Readable.fromWeb(response.body as any);
} else {
throw new Error("Response body is null");