




















@@ -292,25 +292,58 @@ async function retryRpcCall(method, params, options) {
292292function isRetryableGatewayCallError(error) {
293293const text = error instanceof Error ? error.message : String(error);
294294return (
295+isRetryableTransientNetworkError(error) ||
295296text.includes("gateway starting") ||
296297text.includes("gateway closed") ||
297298text.includes("handshake timeout") ||
298-text.includes("GatewayTransportError") ||
299-text.includes("ECONNREFUSED") ||
300-text.includes("fetch failed")
299+text.includes("GatewayTransportError")
301300);
302301}
303302304-async function fetchJson(url) {
305-const response = await fetch(url);
306-const text = await response.text();
307-let body = null;
308-try {
309-body = text ? JSON.parse(text) : null;
310-} catch {
311-body = text;
303+function isRetryableTransientNetworkError(error, seen = new Set()) {
304+if (!error || seen.has(error)) {
305+return false;
306+}
307+seen.add(error);
308+const candidate = error;
309+const message = candidate instanceof Error ? candidate.message : String(candidate);
310+const code = typeof candidate === "object" && candidate !== null ? candidate.code : undefined;
311+const text = `${String(code ?? "")} ${message}`;
312+if (
313+/\b(?:ECONNRESET|ECONNREFUSED|ETIMEDOUT|EPIPE|EHOSTUNREACH|ENETUNREACH)\b/iu.test(text) ||
314+/\b(?:fetch failed|socket hang up|connection reset)\b/iu.test(text)
315+) {
316+return true;
317+}
318+if (typeof candidate === "object" && candidate !== null && "cause" in candidate) {
319+return isRetryableTransientNetworkError(candidate.cause, seen);
320+}
321+return false;
322+}
323+324+export async function fetchJson(url, options = {}) {
325+const attempts = Math.max(1, options.attempts ?? 3);
326+let lastError;
327+for (let attempt = 1; attempt <= attempts; attempt += 1) {
328+try {
329+const response = await (options.fetchImpl ?? fetch)(url);
330+const text = await response.text();
331+let body = null;
332+try {
333+body = text ? JSON.parse(text) : null;
334+} catch {
335+body = text;
336+}
337+return { ok: response.ok, status: response.status, body };
338+} catch (error) {
339+lastError = error;
340+if (attempt >= attempts || !isRetryableTransientNetworkError(error)) {
341+throw error;
342+}
343+await delay(options.retryDelayMs ?? 250);
344+}
312345}
313-return { ok: response.ok, status: response.status, body };
346+throw lastError ?? new Error(`fetch ${url} failed`);
314347}
315348316349function configureKitchenSink(env, port) {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。