اسکریپت Node.js شما هفتههاست هر ده دقیقه یک بار همان API را فراخوانی میکند. آن را به یک سرور CI منتقل میکنید، یا همکارتان آن را در شبکه دفتر اجرا میکند، و حالا هر فراخوانی با یک سطر تمام میشود: TypeError: fetch failed. نه نام میزبانی در کار است، نه کد وضعیتی. آدرس در مرورگر باز میشود و curl هم از همان ماشین به آن میرسد، پس پیام انگار هیچ چیزی نمیگوید.
اما چیزی میگوید، فقط نه در خود پیام. این راهنما نشان میدهد Node.js علت واقعی را کجا نگه میدارد، علتهایی که روی Node.js 22، 24 و 26 بازتولید کردیم کداماند، چرا fetch متغیرهای پروکسی را نادیده میگیرد، کدام ناسازگاری undici روی Node.js 26 agentهای پروکسی را از کار میاندازد، و اسکریپتی آزمودهشده را معرفی میکند که علت را نام میبرد.
خطای TypeError: fetch failed یعنی چه؟
تابع سراسری fetch() در Node.js، که از نسخه 18 در دسترس است، بر پایه undici ساخته شده است؛ undici یک کلاینت HTTP است که درون هر نسخه Node.js عرضه میشود. استاندارد Fetch میگوید درخواستی که به خطای شبکه ختم شود با یک TypeError رد (reject) میشود. undici برای همه این خطاها یک پیام ثابت به کار میبرد، یعنی fetch failed، و خطای اصلی را بهعنوان cause به آن میافزاید.
خطای شبکه هر چیزی است که پیش از رسیدن پاسخ خراب شود: جستوجوی نام، اتصال TCP، دستدادن TLS، تونل پروکسی یا اتصالی که پیش از رسیدن هدرها بسته شود. خطای 404 یا 500 خطای شبکه نیست؛ fetch بهطور عادی پاسخ را برمیگرداند و res.ok برابر false است.
وقتی هیچ کدی خطا را نگیرد، Node.js علت را خودش چاپ میکند. این خروجی واقعی یک اسکریپت یکخطی با نام میزبان غلط روی Node.js 26.10.0 است:
[TypeError: fetch failed] {
[cause]: Error: getaddrinfo ENOTFOUND api.example-typo.invalid
at GetAddrInfoReqWrap.onlookupall [as oncomplete] (node:dns:122:26) {
errno: -3008,
code: 'ENOTFOUND',
syscall: 'getaddrinfo',
hostname: 'api.example-typo.invalid'
}
}مشکل وقتی شروع میشود که کد خطا را بگیرد و فقط پیام را لاگ کند، همان کاری که بسیاری از چارچوبها (فریمورکها) و SDKها انجام میدهند. چاپ علت فقط یک سطر بیشتر لازم دارد:
try {
await fetch("https://api.example-typo.invalid/v1/items");
} catch (err) {
console.log(err.message); // fetch failed
console.log(err.cause?.code, err.cause?.message); // ENOTFOUND getaddrinfo ENOTFOUND api.example-typo.invalid
}چگونه خطای واقعی پشت fetch failed را پیدا کنیم؟
- بهجای
err.message، مقدارerr.causeرا لاگ کنید. اگر چارچوبی آن را پنهان میکند، فراخوانی را خودتان در یک try/catch بپیچید. cause.codeرا بخوانید. کدهایی که باEشروع میشوند (ENOTFOUND،ECONNRESET) از سیستمعامل میآیند، کدهایی که باUND_ERR_شروع میشوند از undici، و کدهایی مانندUNABLE_TO_GET_ISSUER_CERT_LOCALLYاز لایه TLS.- دنبال
AggregateErrorبگردید. برایlocalhost، Node.js چند آدرس را امتحان میکند. در آزمون ماcause.messageخالی بود وcause.errorsیکECONNREFUSEDبرای::1و یکی برای127.0.0.1داشت. - در خطاهای پروکسی یک سطح عمیقتر بروید. ورود ردشده به پروکسی پیام
Request was cancelled.را بدون کد داد؛ کد وضعیت درerr.cause.causeبود:Proxy response (407) !== 200 when HTTP Tunneling. - به زمان دقت کنید. چند میلیثانیه یعنی رد شدن اتصال، بازنشانی یا پاسخ DNS؛ حدود 10 ثانیه یعنی مهلت اتصال؛ پنج دقیقه یعنی مهلت پیشفرض انتظار برای هدرها.
کدهای err.cause: معنای هرکدام و آنچه باید رفع کنید
همه ردیفها را روی ویندوز 11 با Node.js 24.21.0 و 26.10.0 تولید کردیم، با یک پروکسی آزمون محلی و سرورهای محلی که اتصال را رد یا بازنشانی میکنند، معطل میمانند یا گواهی آزمایشی ارائه میدهند؛ هر دو نسخه کدهای یکسانی دادند. مرجع خطاهای undici کدهای UND_ERR_ را مستند کرده و توصیه میکند بهجای instanceof با error.code مقایسه کنید، چون dispatcher ممکن است از نسخه دیگری از undici آمده باشد.
cause.code و پیام | چه رخ داد | نخست چه را بررسی کنید |
|---|---|---|
ENOTFOUND getaddrinfo ENOTFOUND host | نام میزبان به آدرسی تبدیل نمیشود | املا، DNS، VPN |
ECONNREFUSED connect ECONNREFUSED 127.0.0.1:8999 | هیچ برنامهای روی آن پورت منتظر نیست | سرویس، پورت، پورت پروکسی |
ECONNREFUSED درون یک AggregateError | همان، برای همه آدرسهای localhost | سرور را راه بیندازید |
UND_ERR_CONNECT_TIMEOUT Connect Timeout Error | ظرف 10 ثانیه اتصال TCP برقرار نشد | آدرس، فایروال |
ECONNRESET read ECONNRESET | با بازنشانی TCP قطع شد | پروکسی، فایروال؛ تلاش دوباره |
UND_ERR_SOCKET other side closed | پیش از هر پاسخی بسته شد | لاگهای سرور؛ تلاش دوباره |
UND_ERR_HEADERS_TIMEOUT Headers Timeout Error | وصل شد، اما هدرها بهموقع نرسیدند | محدودیت زمانی خودتان |
UNABLE_TO_GET_ISSUER_CERT_LOCALLY، SELF_SIGNED_CERT_IN_CHAIN | گواهی ریشه مورد اعتماد نیست | گواهی ریشه شرکت |
UNABLE_TO_VERIFY_LEAF_SIGNATURE | سرور گواهی میانی را نفرستاد | زنجیره گواهی سرور |
ERR_SSL_WRONG_VERSION_NUMBER | TLS به یک پورت HTTP ساده فرستاده شد | https:// در آدرس پروکسی |
UND_ERR_INVALID_ARG invalid onError method | agent از نسخه اصلی دیگری از undici | fetch خود undici |
| بدون کد: Request was cancelled. | پروکسی تونل را رد کرد | اطلاعات ورود پروکسی |
یک پیام مرتبط fetch failed نیست: اگر هدرها برسند اما بدنه معطل بماند، res.text() یا res.json() خطای TypeError: terminated را پرتاب میکند که در آزمون ما علتش UND_ERR_BODY_TIMEOUT بود.
ENOTFOUND و ECONNREFUSED: درخواست هرگز به سروری نرسید
ENOTFOUND از جستوجوی نام میآید: سرور DNS (resolver) پاسخ داده که چنین نامی وجود ندارد. دنبال غلط املایی، یک VPN با سرورهای DNS مخصوص خودش یا کانتینری بگردید که به DNS داخلی شرکت شما دسترسی ندارد. پشت پروکسی، نام را خود پروکسی پیدا میکند، پس میزبان نادرست به شکل کد وضعیت پروکسی برمیگردد: پروکسی آزمون ما 502 داد که به شکل Proxy response (502) !== 200 when HTTP Tunneling گزارش شد.
ECONNREFUSED یعنی ماشین پاسخ داده، اما هیچ برنامهای روی آن پورت اتصال نمیپذیرد. اگر آدرس درون پیام آدرس پروکسی شماست، تنظیم پروکسی نادرست است، نه مقصد. با localhost، Node.js هم ::1 و هم 127.0.0.1 را امتحان میکند؛ در آزمون ما سروری که فقط روی 127.0.0.1 گوش میداد همچنان به http://localhost پاسخ داد. وقتی هیچکدام پاسخ نمیدهند، سرور توسعه خاموش است، از پورت دیگری استفاده میکند، یا کد شما در Docker اجرا میشود که در آن localhost خود کانتینر است.
ECONNRESET، "other side closed" و "socket hang up"
هر سه یعنی اتصالی باز شده و سپس قطع شده است. fetch را با ماژول http و Axios 1.20 مقایسه کردیم:
- سروری که با بازنشانی TCP پاسخ داد، در هر سه به
read ECONNRESETانجامید. - سروری که اتصال را بدون پاسخ بست، در fetch به
UND_ERR_SOCKET(other side closed) و درhttpو Axios بهsocket hang upبا کدECONNRESETانجامید.
دلیلهای رایج: سرور در میانه درخواست از کار افتاده، یک متعادلکننده بار (load balancer) یا پروکسی یک اتصال keep-alive بیکار را، یعنی اتصالی که برای درخواستهای بعدی باز نگه داشته میشود، درست در لحظهای بسته که کلاینت شما دوباره از آن استفاده کرده، یا فایروالی یک اتصال طولانی را قطع کرده است. برخی سرورها و فیلترهای ربات هم اتصالهایی را که نمیخواهند به آنها پاسخ دهند میبندند؛ معنایش این است که سرعت را کم کنید یا اجازه دسترسی بخواهید، نه اینکه پافشارانهتر تلاش کنید.
درخواستهای idempotent، یعنی درخواستهایی که تکرارشان نتیجه را تغییر نمیدهد (GET و HEAD)، را یکی دو بار با تأخیری فزاینده دوباره بفرستید. درخواست POST را که سفارشی ثبت میکند کورکورانه تکرار نکنید: ممکن است سرور پیش از قطع اتصال کار را انجام داده باشد.
مهلتها: UND_ERR_CONNECT_TIMEOUT، UND_ERR_HEADERS_TIMEOUT و AbortSignal.timeout()
fetch در Node.js هیچ محدودیت زمانی کلی ندارد. undici پس از 10 ثانیه از برقراری اتصال TCP دست میکشد، سپس تا 300 ثانیه برای هدرها و تا 300 ثانیه میان تکههای بدنه صبر میکند. سروری که اتصال را بپذیرد و معطل بماند میتواند یک درخواست را پنج دقیقه نگه دارد. محدودیت خودتان را تعیین کنید:
try {
const res = await fetch("https://api.example.com/v1/items", { signal: AbortSignal.timeout(5_000) });
console.log(res.status);
} catch (err) {
if (err.name === "TimeoutError") console.log("gave up after 5 s");
else console.log(err.message, err.cause?.code);
}در برابر یک سرور محلی که هرگز پاسخ نمیدهد، این کد روی Node.js 24 و 26 عبارت gave up after 5 s را چاپ کرد. همانطور که صفحه MDN درباره AbortSignal.timeout() (به انگلیسی) میگوید، سیگنال با یک TimeoutError لغو میشود، نه با fetch failed، پس err.name را بررسی کنید. این محدودیت بدنه را هم در بر میگیرد: بدنهای که معطل ماند باعث شد res.text() همان TimeoutError را پرتاب کند. تغییر محدودیتهای خود undici به یک Agent از بسته npm نیاز دارد و این، مسئله نسخه را پیش میکشد که در ادامه به آن میپردازیم.
چرا fetch متغیرهای HTTP_PROXY و HTTPS_PROXY را نادیده میگیرد؟
curl، pip و بسیاری از ابزارهای دیگر متغیرهای محیطی پروکسی را میخوانند؛ fetch در Node.js نمیخواند. با تنظیم HTTPS_PROXY، fetch روی Node.js 22.23.3، 24.21.0 و 26.10.0 مستقیم به مقصد رفت و لاگ پروکسی ما خالی ماند، در حالی که curl از پروکسی استفاده کرد. جایی که فقط پروکسی به اینترنت دسترسی دارد، علت خطا مقصد را نام میبرد (ENOTFOUND، UND_ERR_CONNECT_TIMEOUT)، هرگز پروکسیای را که کنار گذاشته شده است.
پشتیبانی داخلی Node.js از پروکسی این رفتار را روشن میکند: NODE_USE_ENV_PROXY=1 (Node.js 22.21.0 و 24.0.0 یا بالاتر) یا پرچم --use-env-proxy (22.21.0 و 24.5.0 یا بالاتر). در این حالت Node.js متغیرهای HTTP_PROXY، HTTPS_PROXY و NO_PROXY را هنگام شروع میخواند، هم برای fetch و هم برای ماژولهای http و https. مستندات هنوز این قابلیت را «در حال توسعه فعال» علامت زده است.
NODE_USE_ENV_PROXY=1 HTTPS_PROXY="http://user:pass@pr.proxynet.io:8000" NO_PROXY="localhost,127.0.0.1" node app.mjsدر PowerShell:
$env:NODE_USE_ENV_PROXY = "1"
$env:HTTPS_PROXY = "http://user:pass@pr.proxynet.io:8000"
node app.mjsسپس پروکسی آزمون ما روی هر سه نسخه CONNECT example.com:443 را ثبت کرد؛ Node.js 22 همچنین هشدار داد که EnvHttpProxyAgent آزمایشی است. سه جزئیات بسیاری را غافلگیر میکند:
- آدرس پروکسی با
http://شروع میشود، حتی برای مقصدهایhttps://. درخواست تونل، HTTP ساده است و TLS درون آن اجرا میشود؛https://در ابتدای آدرس یک پروکسی HTTP ساده بهERR_SSL_WRONG_VERSION_NUMBERانجامید. http.setGlobalProxyFromEnv()همین کار را از درون کد انجام میدهد، از Node.js 24.14.0 و 25.4.0 به بعد؛ Node.js 22 آن را ندارد.- رمز نادرست یک سطح عمیقتر پنهان میشود، در
err.cause.cause.
آدرس اتصال (gateway) یک ارائهدهنده، مانند پروکسی مسکونی ما، به شکل http://user:pass@pr.proxynet.io:8000 در همان متغیر قرار میگیرد، یا وقتی IP سرور شما در لیست سفید (whitelist) باشد، بدون اطلاعات ورود.
برای تعیین پروکسی به تفکیک درخواست، Axios و SOCKS5، راهنمای ما درباره استفاده از پروکسی در Node.js را دنبال کنید.
Node.js 26 و "invalid onError method": ناسازگاری نسخه undici
هر نسخه Node.js undici مخصوص خودش را همراه دارد که از بسته undici در npm جداست: Node.js 22.23.3 همراه undici 6.28.1، Node.js 24.21.0 همراه 7.29.1 و Node.js 26.10.0 همراه 8.10.2 عرضه میشود. undici 8.0.0 پوششهایی (wrapper) را حذف کرد که به کد handler قدیمیتر اجازه میداد با کد تازهتر کار کند. بر اساس برنامه انتشار Node.js، Node.js 26 در 28 اکتبر 2026 شاخه Active LTS (پشتیبانی بلندمدت فعال) میشود، پس بسیاری از پروژهها هنگام ارتقا با این مشکل روبهرو خواهند شد.
این خطا وقتی ظاهر میشود که یک ProxyAgent یا Agent از بسته npm بهعنوان dispatcher به fetch سراسری داده شود. چهار نسخه اصلی (major) از بسته npm را روی سه نسخه Node.js آزمودیم:
| undici در npm | Node.js 22.23.3 | Node.js 24.21.0 | Node.js 26.10.0 |
|---|---|---|---|
| 5.29.0 | کار میکند | کار میکند | invalid onError method |
| 6.29.0 | کار میکند | کار میکند | invalid onError method |
| 7.30.0 | کار میکند | کار میکند | کار میکند |
| 8.11.2 | invalid onRequestStart method | invalid onRequestStart method | کار میکند |
هر شکست TypeError: fetch failed بود، با UND_ERR_INVALID_ARG بهعنوان علت. گونه بیصداتر بدتر است: روی Node.js 26، setGlobalDispatcher(new ProxyAgent(...)) از undici 5 یا 6 هیچ خطایی نداد، fetch آن را نادیده گرفت و درخواست مستقیم بیرون رفت، در حالی که پروکسی ما چیزی ندید.
راه رفع این است که fetch را از همان بستهای بردارید که agent را از آن برداشتهاید:
import { fetch, ProxyAgent } from "undici"; // fetch and the agent from the same package
const proxy = new ProxyAgent("http://user:pass@pr.proxynet.io:8000");
const res = await fetch("https://example.com/", { dispatcher: proxy });
console.log(res.status); // 200 in our test, through a local test proxyاین روش با undici 8.11.2 روی Node.js 24 و 26 کار کرد. اگر ناسازگاری در ابزاری است که خودتان ننوشتهاید، مثلاً یک CLI که undici قدیمی را درون خود دارد و از متغیرهای پروکسی شما agent میسازد، ابزار را بهروز کنید یا تا رفع مشکل، آن را روی Node.js 24 نگه دارید.
خطاهای گواهی پشت fetch failed
بررسی ناموفق TLS هم به شکل fetch failed میرسد و کد گواهی در cause.code قرار دارد. در محیط کار، منشأ معمول یک پروکسی شرکتی یا آنتیویروس است که ترافیک HTTPS را بازرسی میکند و آن را با گواهی ریشه خودش دوباره امضا میکند. Node.js گواهیها را با فهرست داخلی خودش از مراجع ریشه بررسی میکند، نه با فهرست سیستمعامل، پس آن گواهی ریشه را با UNABLE_TO_GET_ISSUER_CERT_LOCALLY یا SELF_SIGNED_CERT_IN_CHAIN رد میکند.
متغیر NODE_EXTRA_CA_CERTS را به یک فایل PEM حاوی گواهی ریشه شرکت اشاره دهید، یا اگر گواهی ریشه روی ماشین نصب است، Node.js را با --use-system-ca اجرا کنید (از 22.15.0 و 23.8.0 به بعد). در آزمون ما NODE_EXTRA_CA_CERTS حالت نخست را رفع کرد اما UNABLE_TO_VERIFY_LEAF_SIGNATURE را نه؛ در آن حالت سرور گواهی میانی خود را نمیفرستد و فقط صاحب سرور میتواند آن را درست کند. راهحلها برای npm، Git، Python و curl در خطای Unable to Get Local Issuer Certificate آمده است.
تابعی پوششی برای fetch که علت را نام میبرد و دوباره تلاش میکند
این اسکریپت از fetch سراسری استفاده میکند، پس به هیچ بستهای نیاز ندارد. کل زنجیره علتها را پیمایش میکند، هر تلاش را با AbortSignal.timeout() محدود میکند و فقط بازنشانیها، سوکتهای بستهشده و پایان مهلتها را دوباره امتحان میکند، با انتظار نمایی (exponential backoff)، یعنی فاصلهای که در هر تلاش دو برابر میشود، بهعلاوه یک جزء تصادفی (jitter). شکست DNS، رد شدن اتصال، خطای گواهی و ورود ردشده به پروکسی هر بار به همان شکل تکرار میشوند، پس اسکریپت روی آنها متوقف میشود. در پاسخ به 429 و 503 منتظر Retry-After میماند.
پاسخ 429 یعنی سرور از شما میخواهد سرعت را کم کنید؛ معنای این هدر و شیوه تنظیم سرعت یک کار را در خطای HTTP 429 Too Many Requests توضیح دادهایم.
// fetch-check.mjs: name the real cause behind "TypeError: fetch failed" and retry only what can recover.
// Tested on Node.js 22, 24 and 26. Optional proxy: NODE_USE_ENV_PROXY=1 HTTPS_PROXY=http://user:pass@pr.proxynet.io:8000
const RETRY_CODES = new Set(["ECONNRESET", "UND_ERR_SOCKET", "UND_ERR_CONNECT_TIMEOUT"]);
const CERT_CODES = /CERT|SELF_SIGNED|UNABLE_TO_(GET|VERIFY)/;
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
/** The error and every error wrapped inside it, outermost first. */
function causes(err) {
const chain = [];
for (let e = err; e && chain.length < 8; e = e.cause) {
chain.push(e);
if (e instanceof AggregateError) chain.push(...e.errors); // "localhost": one error per address
}
return chain;
}
/** The first string error code in the chain, such as "ECONNRESET". */
const codeOf = (err) => causes(err).map((e) => e.code).find((c) => typeof c === "string") ?? "";
/** One line: what failed and what to check first. */
function explain(err) {
if (err.name === "TimeoutError") return "no answer before our AbortSignal.timeout(): slow server or proxy";
const chain = causes(err);
const text = chain.map((e) => e.message).join(" | ");
const code = codeOf(err);
const tunnel = text.match(/Proxy response \((\d{3})\)/);
if (tunnel?.[1] === "407") return "proxy wants credentials (407): check user:pass or the IP whitelist";
if (tunnel) return `proxy refused the tunnel (${tunnel[1]}): the proxy could not or would not reach the target`;
if (code === "ENOTFOUND") return `name ${chain.find((e) => e.hostname)?.hostname} does not resolve: typo, DNS or VPN`;
if (code === "ECONNREFUSED") return "nothing listens on that address and port: wrong port, service down or wrong proxy port";
if (code === "ECONNRESET") return "the connection was cut (TCP reset): server, proxy or firewall dropped it";
if (code === "UND_ERR_SOCKET") return "the other side closed the connection before answering";
if (code === "UND_ERR_CONNECT_TIMEOUT") return "no TCP connection within 10 s: wrong IP, firewall or blocked outbound port";
if (code === "UND_ERR_HEADERS_TIMEOUT") return "connected, but no response headers in time";
if (code === "ERR_SSL_WRONG_VERSION_NUMBER") return "TLS spoken to a plain-HTTP port: the proxy URL should start with http://";
if (CERT_CODES.test(code)) return `certificate not trusted (${code}): add your CA with NODE_EXTRA_CA_CERTS`;
return `unrecognised, read the innermost error: ${chain.at(-1).message}`;
}
/** GET with a hard time limit, backoff for network blips, and Retry-After for 429/503. */
async function getWithRetry(url, { attempts = 3, timeoutMs = 15_000 } = {}) {
for (let attempt = 1; ; attempt++) {
const backoff = 500 * 2 ** (attempt - 1) + Math.random() * 250; // 0.5 s, 1 s, 2 s ... plus jitter
let res;
try {
res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
} catch (err) {
const code = codeOf(err);
const retryable = RETRY_CODES.has(code) || err.name === "TimeoutError";
if (!retryable || attempt === attempts) throw err;
console.log(` attempt ${attempt}: ${code || err.name}, retrying in ${Math.round(backoff)} ms`);
await sleep(backoff);
continue;
}
if ((res.status === 429 || res.status === 503) && attempt < attempts) {
const wait = Number(res.headers.get("retry-after")) * 1000 || backoff; // seconds form only
await res.body?.cancel();
console.log(` attempt ${attempt}: HTTP ${res.status}, waiting ${Math.round(wait)} ms`);
await sleep(wait);
continue;
}
return res;
}
}
for (const url of process.argv.slice(2)) {
console.log(url);
try {
const res = await getWithRetry(url, { timeoutMs: 5_000 });
console.log(` OK: HTTP ${res.status}`);
} catch (err) {
console.log(` FAILED: ${err.name}: ${err.message} -> ${explain(err)}`);
}
}خروجی چه شکلی دارد
اسکریپت را روی Node.js 26.10.0 در برابر یک نام میزبان غلط، یک پورت بسته روی localhost و سرورهای محلی اجرا کردیم که هر اتصال را بازنشانی میکنند (پورت 8902)، هرگز پاسخ نمیدهند (8901)، گواهی صادرشده از مرجع آزمایشی خودمان را به کار میبرند (8906) و دو بار 429 پاسخ میدهند و سپس موفق میشوند (8910):
https://api.example-typo.invalid/
FAILED: TypeError: fetch failed -> name api.example-typo.invalid does not resolve: typo, DNS or VPN
http://localhost:8999/
FAILED: TypeError: fetch failed -> nothing listens on that address and port: wrong port, service down or wrong proxy port
http://127.0.0.1:8902/
attempt 1: ECONNRESET, retrying in 652 ms
attempt 2: ECONNRESET, retrying in 1115 ms
FAILED: TypeError: fetch failed -> the connection was cut (TCP reset): server, proxy or firewall dropped it
http://127.0.0.1:8901/
attempt 1: TimeoutError, retrying in 658 ms
attempt 2: TimeoutError, retrying in 1024 ms
FAILED: TimeoutError: The operation was aborted due to timeout -> no answer before our AbortSignal.timeout(): slow server or proxy
https://127.0.0.1:8906/
FAILED: TypeError: fetch failed -> certificate not trusted (UNABLE_TO_GET_ISSUER_CERT_LOCALLY): add your CA with NODE_EXTRA_CA_CERTS
http://127.0.0.1:8910/items
attempt 1: HTTP 429, waiting 1000 ms
attempt 2: HTTP 429, waiting 1000 ms
OK: HTTP 200سپس از طریق پروکسی آزمون محلی با NODE_USE_ENV_PROXY=1: با رمز درست، با رمز نادرست، و با https:// در ابتدای آدرس پروکسی:
https://example.com/
OK: HTTP 200
https://example.com/
FAILED: TypeError: fetch failed -> proxy wants credentials (407): check user:pass or the IP whitelist
https://example.com/
FAILED: TypeError: fetch failed -> TLS spoken to a plain-HTTP port: the proxy URL should start with http://Node.js 22.23.3 و 24.21.0 همین سطرها را چاپ کردند، جز مقدارهای تصادفی زمان انتظار.
کجا با این خطا روبهرو میشوید
- Next.js و دیگر چارچوبهای سمت سرور، که صفحههای خطایشان اغلب فقط پیام را نشان میدهد.
- سرورهای MCP و ابزارهای عامل هوش مصنوعی، که اغلب پشت VPN یا پروکسی شرکتی اجرا میشوند.
- SDKهای ساختهشده بر پایه fetch، که برخی از آنها در نسخههای قدیمیتر علت را دور میریختند.
- سرورهای CI و ساختهای Docker، که متغیرهای پروکسی و گواهی ریشه شرکتی ماشین میزبان را ندارند.
- کارهای اسکرپینگ و پایش، که در اجراهای طولانی با بیشتر ردیفهای جدول بالا روبهرو میشوند.
کارهای طولانی جمعآوری داده بیشترین سود را از این تابع پوششی میبرند. اگر چنین کاری با چند مقصد مشخص سروکار دارد و به IPهای خروجی نیاز دارد که هفتهها ثابت بمانند، یک پروکسی دیتاسنتر آدرسهای IPv4 اختصاصی با ترافیک بدون سهمیه در اختیارتان میگذارد.
اشتباههای رایج
- لاگ کردن
err.message. همیشه میگویدfetch failed. - انتظار اینکه fetch متغیر
HTTPS_PROXYرا بخواند. بدونNODE_USE_ENV_PROXY=1یا--use-env-proxyنمیخواند. - دادن agent بسته undici در npm به fetch سراسری.
fetchرا از همان بسته وارد کنید. - خاموش کردن بررسی گواهی.
NODE_TLS_REJECT_UNAUTHORIZED=0اعتبارسنجی را برای کل فرایند غیرفعال میکند، پس مهاجمی که در مسیر نشسته درست مانند پروکسی شرکت شما دیده میشود. - تلاش دوباره برای همه چیز. غلط املایی هر بار به همان شکل شکست میخورد؛ یک POST پس از
ECONNRESETممکن است دو بار اجرا شود. - نبود محدودیت زمانی. سروری که معطل بماند میتواند یک درخواست را پنج دقیقه نگه دارد.
راهنمای انتخاب
آنچه در err.cause میبینید | چه کنید |
|---|---|
ENOTFOUND | نام میزبان یا DNS را اصلاح کنید؛ تلاش دوباره لازم نیست |
ECONNREFUSED با آدرس پروکسی شما | میزبان و پورت پروکسی را دوباره کپی کنید |
ECONNREFUSED روی localhost | سرور را راه بیندازید؛ پورت و Docker را بررسی کنید |
ECONNRESET، UND_ERR_SOCKET | درخواستهای GET را با انتظار نمایی دوباره بفرستید |
UND_ERR_CONNECT_TIMEOUT | فایروال را بررسی کنید؛ پشت پروکسی شرکتی NODE_USE_ENV_PROXY=1 را تنظیم کنید |
انتظار طولانی و سپس UND_ERR_HEADERS_TIMEOUT | AbortSignal.timeout() اضافه کنید |
| یک کد گواهی | NODE_EXTRA_CA_CERTS یا --use-system-ca |
Proxy response (407) یک سطح عمیقتر | user:pass یا لیست سفید IP را بررسی کنید |
invalid onError method | fetch را از بسته undici همان agent وارد کنید |
پرسشهای متداول
چرا fetch در پاسخ 404 یا 500 خطا پرتاب نمیکند؟
چون سرور پاسخ داده است. fetch فقط در خطاهای شبکه رد میشود؛ خطای HTTP بهطور عادی برمیگردد و res.ok برابر false است، پس پیش از خواندن بدنه آن را بررسی کنید.
آیا "fetch failed" همان "Failed to fetch" در مرورگر است؟
به هم مربوطاند، اما یکی نیستند. کروم با TypeError: Failed to fetch رد میشود و دلیل را به صفحه نمیگوید، پس یک مسدودسازی CORS، یعنی قاعده مرورگر برای درخواست به دامنهای دیگر، دقیقاً همین شکل را دارد؛ در DevTools زبانه Network (شبکه) را باز کنید. Node.js دلیل را در cause میگذارد.
چگونه برای fetch در Node.js مهلت زمانی تعیین کنم؟
signal: AbortSignal.timeout(ms) را بدهید. وقتی مهلت تمام شود، fetch با یک TimeoutError رد میشود و این محدودیت بدنه معطلمانده را هم متوقف میکند.
آیا fetch در Node.js از HTTP_PROXY و HTTPS_PROXY استفاده میکند؟
فقط با NODE_USE_ENV_PROXY=1 (22.21.0، 24.0.0 و بالاتر) یا --use-env-proxy (22.21.0، 24.5.0 و بالاتر). در غیر این صورت این متغیرها بیصدا نادیده گرفته میشوند.
چرا curl کار میکند اما fetch روی همان ماشین شکست میخورد؟
معمولاً به یکی از دو دلیل: curl متغیرهای پروکسی را میخواند و fetch نه، و بسیاری از نسخههای curl، از جمله نسخه درون ویندوز، از مخزن گواهی سیستم استفاده میکنند، در حالی که Node.js فهرست خودش را به کار میبرد، مگر اینکه --use-system-ca را بدهید.
آیا پروکسی میتواند "TypeError: fetch failed" را رفع کند؟
فقط وقتی علت، مسیر شبکه است، مثلاً شبکه شرکتی که در آن فقط پروکسی به اینترنت دسترسی دارد. پروکسی غلط املایی، مشکل گواهی یا سروری را که خاموش است رفع نمیکند، و راهی برای گذشتن از سایتی نیست که اتصال شما را بازنشانی میکند یا نرخ درخواستهایتان را محدود میکند: سرعت را کم کنید، از API رسمی آن استفاده کنید یا اجازه دسترسی بخواهید.
خلاصه
TypeError: fetch failed یک پوشش است: دلیل در err.cause است، گاهی یک سطح عمیقتر. کد را بخوانید، آنچه را نام میبرد رفع کنید، به هر فراخوانی یک AbortSignal.timeout() بدهید و فقط بازنشانیها و پایان مهلتها را دوباره امتحان کنید. پشت پروکسی، NODE_USE_ENV_PROXY=1 را تنظیم کنید، http:// را در آدرس پروکسی نگه دارید و fetch و agent آن را از یک بسته undici بردارید، بهویژه روی Node.js 26. وقتی کد شما برای پروکسی آماده شد، گزینهها را در صفحه خدمات پروکسی ما مقایسه کنید.




