كل شرح تعليمي لـ Node.js fetch يعلّمك await fetch(url) ثم يتوقف عند هذا الحد. بعدها قد يبتلع تطبيقك في الإنتاج خطأ 500 بصمت، أو تتعطل إحدى الطلبات 90 ثانية كاملة من دون مهلة زمنية، ثم تجد نفسك مساء الجمعة تصلح مشكلة كان ينبغي أن تكون واضحة من البداية.
أعمل منذ فترة على بناء الأدوات الداخلية وخطوط معالجة البيانات في Thunderbit، وأستطيع أن أقول لك: الفجوة بين «fetch تعمل في الشرح التعليمي» و«fetch تعمل في الإنتاج» هي مصدر معظم المتاعب. وقد لخّص أحد المطورين على Reddit الأمر بدقة: «عندما تنتقل إلى الإنتاج، تدرك أنك تحتاج شيئًا أكثر مرونة من fetch الأصلية.»
واعترف آخر: «عملت ثلاث سنوات كمطور ويب، واليوم فقط عرفت أن كتلة catch في fetch API ليست لأخطاء HTTP.» هذا الدليل يغطي الأشياء الخمسة التي تتجاهلها معظم الشروح — فخ الأخطاء، ومهلات AbortController، ومنطق إعادة المحاولة، وإعادة استخدام الاتصالات، ومتى يجب تجاوز fetch نحو الاستخراج المنظم للبيانات. إذا سبق أن فشل طلب fetch بصمت في الإنتاج، فهذا المقال لك.

ما هي واجهة Node.js Fetch API؟
واجهة Node.js Fetch API هي الطريقة المدمجة والمتوافقة مع المتصفح لإرسال طلبات HTTP (GET وPOST وPUT وDELETE وغيرها) من داخل Node.js — من دون تثبيت Axios أو node-fetch أو أي حزمة أخرى. إذا كنت قد استخدمت fetch() في المتصفح، فأنت تعرف الصيغة بالفعل. الآن تعمل الواجهة نفسها على الخادم أيضًا.
إليك ملخصًا سريعًا لتاريخ الإصدارات:
| المرحلة | إصدار Node | ما الذي حدث |
|---|---|---|
| ميزة fetch التجريبية | v17.5.0 / v16.15.0 | أُضيفت fetch خلف --experimental-fetch |
| fetch عالمي افتراضي | v18.0.0 | أصبحت fetch التجريبية متاحة عالميًا، بدعم من Undici |
| fetch مستقرة | v21.0.0 | لم تعد تجريبية |
| خط الأساس للإنتاج في 2026 | v22 LTS / v24 LTS | موصى به للإنتاج؛ أما v20 فقد انتهى دعمه الآن |
في الخلفية، تعمل fetch في Node بواسطة Undici — وهو عميل HTTP عالي الأداء صُمم خصيصًا لـ Node.js. ولا يعتمد على وحدة http القديمة المدمجة. والفائدة العملية: تحصل على واجهة HTTP حديثة قائمة على Promise وتعمل بالطريقة نفسها في كود المتصفح، وخلفية Express، ودالة serverless، ونصوص CLI.
لماذا تهمك Node.js Fetch API في مشاريعك؟
قبل Node 18، كان كل مشروع جديد يبدأ بالطريقة نفسها: npm install axios أو npm install node-fetch. في 2026، إذا كان مشروعك يعمل على إصدار Node LTS مدعوم، فإن طلبات HTTP الأساسية لا تحتاج أي تبعيات على الإطلاق. وهذه مكسب حقيقي لحجم الحزمة، وأمان سلسلة التوريد، وسهولة الانضمام للمشروع (فأخيرًا يشترك مطورو الواجهة الأمامية والخلفية في الواجهة نفسها).
وهنا تتألق fetch المدمجة:
| الحالة | لماذا تعمل fetch المدمجة جيدًا | ملاحظة إنتاجية |
|---|---|---|
| خلفية Express/Fastify تستدعي REST APIs | async/await مألوفة، بلا تبعية | أضف مهلة زمنية وفحص response.ok |
| الدوال المعدومة الخادم serverless (Lambda وVercel وغيرها) | سطح بدء تشغيل بارد صغير، بلا تثبيت حزم | اجعل المهلة أقل من الحد الأقصى للمنصة |
| نصوص CLI والأتمتة | GET/POST بسيطان من دون إعداد مشروع | أضف إعادة محاولة/تدرجًا زمنيًا للـ APIs غير المستقرة |
| تسليم webhooks أو تمريرها | طرق HTTP ورؤوس معيارية | لا تُعد المحاولة تلقائيًا لطلبات POST غير القابلة للتكرار |
| التقارير ولوحات المعلومات | مناسبة لسحب JSON من الـ APIs | استخدم الترقيم الصفحي وتجمع الاتصالات داخل الحلقات |
| تواصل الخدمات المصغرة | تعمل للنداءات الداخلية البسيطة عبر HTTP | فكّر في Got أو Undici مباشرة لإعادة المحاولة أو الـ hooks أو HTTP/2 |
في مشاريع Node 22+ الجديدة، تكون fetch المدمجة هي الخيار الافتراضي المنطقي — ما لم تكن تعرف أنك تحتاج إلى ميزات لا توفرها (مثل interceptors أو إعادة المحاولة المدمجة أو HTTP/2 وغيرها). وأرقام التحميل من npm تشرح مشهدًا في طور التحول: لا يزال node-fetch يسحب نحو 144.9 مليون تنزيل أسبوعيًا، لكن جزءًا كبيرًا من ذلك يأتي من تبعيات قديمة أو غير مباشرة. Axios عند نحو 108.6 مليون، وUndici عند نحو 106 ملايين، وGot عند نحو 36 مليونًا، وKy عند نحو 5.6 ملايين. والاتجاه واضح: fetch المدمجة هي الخط الأساس الجديد، والعملاء من الطرف الثالث للحاجات الخاصة فقط.
fetch الأصلي مقابل node-fetch مقابل Axios مقابل Got مقابل Ky: مصفوفة القرار لعام 2026
السؤال الأكثر شيوعًا الذي أراه في منتديات المطورين: «أي عميل HTTP يجب أن أستخدمه في Node.js؟» وقد لخّص أحد مستخدمي Reddit الفكرة بقوله: «لماذا أستورد مكتبة… بينما اللغة/الإطار يوفّر الوظيفة مدمجة؟» نقطة عادلة — لكن الجواب يعتمد على ما تحتاجه.

| الميزة | fetch الأصلية | node-fetch v3 | axios | got v15 | ky v2 |
|---|---|---|---|---|---|
| إصدار Node.js | ≥18 (نوصي بـ 22/24 LTS) | ≥12.20 | واسع | ≥22 | ≥22 |
| يتطلب تثبيتًا | لا | نعم | نعم | نعم | نعم |
| دعم ESM + CJS | كلاهما (عالمي) | ESM فقط (v3) | كلاهما | ESM فقط | ESM فقط |
| رفض تلقائي عند 4xx/5xx | لا | لا | نعم | نعم | نعم |
| إعادة المحاولة مدمجة | لا | لا | لا | نعم | نعم |
| معترضات الطلب | لا | لا | نعم | نعم (hooks) | نعم (hooks) |
| دعم البث | Web ReadableStream | نعم | محدود | Streams قوية في Node | مبني على fetch |
| حجم التثبيت/الأثر | 0 KB | نحو 107 KB، و3 تبعيات | نحو 2.8 MB، و4 تبعيات | نحو 355 KB، و12 تبعية | نحو 405 KB، و0 تبعية |
| دعم HTTP/2 | عبر Undici dispatcher | لا | لا | نعم | لا (غلاف على fetch) |
ملاحظة سريعة بشأن صداع ESM/CJS: الإصدار v3 من node-fetch يعمل بـ ESM فقط، وهذا كسر كثيرًا من المشاريع التي كانت تستخدم require(). أما fetch الأصلية فهي عالمية — تعمل في ملفات CJS وESM من دون أي تعقيدات استيراد. إذا كنت عالقًا على node-fetch v2 بسبب CommonJS، فإن fetch الأصلية تحل المشكلة بالكامل.
وبالنسبة لمخاوف الاستقرار المبكرة: نعم، كانت هناك بالفعل أخطاء حقيقية في التنفيذ الأولي لـ fetch في Node 18. وذكر أحد المطورين على Reddit: «واجهت خطأً غريبًا مع fetch الأصلية في Node 18 مؤخرًا، فاضطررت إلى تحويل تطبيقنا.» كان ذلك في 2023. أما في 2026، ومع Node 22 و24 LTS، فقد حُلّت تلك المشكلات. fetch الأصلية جاهزة للإنتاج.
متى تلتزم بـ fetch الأصلية
استخدم fetch الأصلية عندما:
- يعمل مشروعك على Node 22 LTS أو Node 24 LTS.
- تكون الطلبات بسيطة من نوع REST (GET وPOST وPUT وDELETE).
- لا تمانع إضافة غلاف صغير لـ
response.ok، وتحليل JSON، والمهلات الزمنية، وإعادة المحاولة. - تريد صفر تبعيات وتقليل مخاوف سلسلة التوريد.
- تهمك المطابقة بين واجهة المتصفح والخادم.
- تعمل في بيئات serverless أو edge حيث تُفضَّل الواجهات المدمجة.
متى يكون Axios أو Got أو Ky أنسب
Axios هو الخيار المناسب عندما يعتمد فريقك على معترضات الطلب/الاستجابة (مثل تحديث تلقائي لرمز المصادقة، أو رؤوس المستأجرين، أو التسجيل المركزي)، أو عندما تريد الرفض الافتراضي عند أخطاء HTTP، أو تحتاج توافقًا رجعيًا مع إصدارات Node الأقدم.
Got صُمم لخدمات Node ذات التدفق العالي التي تحتاج إلى إعادة محاولة مدمجة، وhooks، ومراحل مهلة متقدمة، وStreams، ومساعدات الترقيم الصفحي، وUnix sockets، وسير عمل proxy/caching، أو دعم HTTP/2. إنه السكين السويسري لأعمال HTTP داخل Node فقط.
Ky هو النقطة المثالية إذا كنت تحب بساطة fetch لكنك تريد تقليل الحشو البرمجي — فهو يضيف إعادة المحاولة، والمهلة، وhooks، وHTTPError في حزمة صغيرة جدًا وبلا تبعيات.
كيف ترسل طلبات GET باستخدام Node.js Fetch API
يبدو طلب GET مع async/await هكذا:
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
const post = await response.json();
console.log(post.title);
// → "sunt aut facere repellat provident occaecati excepturi optio reprehenderit"
وهنا نسخة سلسلة .then() إذا كنت تفضّلها:
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(response => response.json())
.then(post => console.log(post.title))
.catch(error => console.error(error));
كلاهما يعمل. لكن أيًا منهما ليس آمنًا للإنتاج بعد (سأتوسع في ذلك بعد قليل).
قراء الاستجابة الذين يجب أن تعرفهم:
| الطريقة | متى تُستخدم |
|---|---|
response.json() | عندما يعيد الخادم JSON |
response.text() | عندما يعيد الخادم HTML أو نصًا عاديًا أو CSV أو Markdown |
response.arrayBuffer() | عندما تحتاج بيانات ثنائية (صور، ملفات) |
response.body | عندما تحتاج معالجة متدفقة/مجزأة |
والنمط الأفضل — الذي يفحص الأخطاء فعلًا:
async function getPost(id) {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status} ${response.statusText}`);
}
return response.json();
}
const post = await getPost(1);
console.log(post.title);
سطر if (!response.ok) هو الفاصل بين شرح تعليمي وكود إنتاجي. وهنا يكمن أكبر فخ.
كيف ترسل طلبات POST باستخدام Node.js Fetch API
طلبات POST تتبع الشكل نفسه — فقط حدّد الطريقة والرؤوس والجسم:
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: 'دليل fetch في Node',
body: 'fetch في الإنتاج يحتاج إلى معالجة أخطاء.',
userId: 1,
}),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const created = await response.json();
console.log(created.id); // → 101
إرسال أنواع طلبات أخرى (PUT وDELETE وPATCH)
تستخدم PUT وPATCH وDELETE البنية نفسها مع تغيير قيمة method:
// PUT — استبدال كامل
await fetch('https://jsonplaceholder.typicode.com/posts/1', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: 1, title: 'مستبدل', body: 'استبدال كامل', userId: 1 }),
});
// PATCH — تحديث جزئي
await fetch('https://jsonplaceholder.typicode.com/posts/1', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'تحديث جزئي' }),
});
// DELETE
await fetch('https://jsonplaceholder.typicode.com/posts/1', {
method: 'DELETE',
});
فخ body-parser في Express: إذا كنت ترسل JSON إلى خادم Express وعاد req.body بقيمة undefined، فالحل غالبًا هو هذا: استخدم express.json()، لا express.urlencoded(). يحتاج الخادم إلى وسيط express.json() قبل المسار حتى يحلل الأجسام ذات Content-Type: application/json. وهذا أحد أكثر أسئلة Stack Overflow شيوعًا حول Express، ويقع فيه الناس مرارًا.
import express from 'express';
const app = express();
app.use(express.json()); // ← هذا هو المطلوب لأجسام POST بصيغة JSON
app.post('/api/posts', (req, res) => {
res.json({ received: req.body });
});
فخ أخطاء fetch() الذي يدمّر تطبيقات الإنتاج

هنا ينشأ معظم عيوب fetch في الإنتاج.
fetch() لا ترفض الـ promise عند أخطاء HTTP من فئتي 4xx أو 5xx. إنها ترفض فقط عند فشل على مستوى الشبكة — أخطاء DNS، انقطاع الإنترنت، أو الطلبات الملغاة. إذا أعاد الخادم 403 Forbidden أو 500 Internal Server Error، فإن fetch تعتبر ذلك استجابة ناجحة. ولن يعمل .catch() لديك. ولن يلتقط try/catch ذلك. وسيعالج كودك ما أرسله الخادم بكل بساطة.
توثيق MDN يوضح ذلك بجلاء، لكن معظم الشروح تتجاوزه بسرعة. والنتيجة؟ كود كهذا يبدو سليمًا لكنه يبتلع الأخطاء بصمت:
try {
const response = await fetch('https://api.example.com/private');
const data = await response.json(); // ← هذا يُنفَّذ حتى عند 403
console.log('يبدو ناجحًا:', data);
} catch (error) {
// هنا تصل فقط الأعطال على مستوى الشبكة
console.error('تم الالتقاط:', error);
}
وهنا تفصيل سريع لما يلتقطه كل نمط فعليًا:
| النمط | يلتقط أخطاء الشبكة | يلتقط 4xx/5xx | يحلل JSON بأمان | قابل لإعادة الاستخدام |
|---|---|---|---|---|
.then(res => res.json()) الخام | نعم (عبر .catch()) | لا | لا يوجد فحص لنوع المحتوى | لا |
try/catch مع await fetch() | نعم | لا | لا يوجد فحص لنوع المحتوى | لا |
فحص يدوي if (!res.ok) لكل طلب | نعم | نعم | يعتمد على كل طلب | جزئي |
غلاف fetchJSON() مخصص | نعم | نعم | نعم | نعم |
أنشئ غلافًا قابلًا لإعادة الاستخدام باسم fetchJSON()
أنشئ غلافًا واحدًا. واستوردْه في كل مكان. وتوقف عن نسخ if (!response.ok) في كل ملف:
export class HTTPError extends Error {
constructor(message, { status, statusText, url, body }) {
super(message);
this.name = 'HTTPError';
this.status = status;
this.statusText = statusText;
this.url = url;
this.body = body;
}
}
export async function fetchJSON(url, options = {}) {
const response = await fetch(url, {
headers: {
Accept: 'application/json',
...options.headers,
},
...options,
});
const contentType = response.headers.get('content-type') || '';
const isJSON = contentType.includes('application/json');
const body = isJSON ? await response.json().catch(() => null) : await response.text();
if (!response.ok) {
throw new HTTPError(`HTTP ${response.status} ${response.statusText}`, {
status: response.status,
statusText: response.statusText,
url: response.url,
body,
});
}
return body;
}
الآن، عندما يعيد الخادم 403:
try {
const data = await fetchJSON('https://api.example.com/private');
} catch (error) {
if (error instanceof HTTPError) {
console.error(`أعاد الخادم ${error.status}:`, error.body);
} else {
console.error('فشل في الشبكة أو خطأ آخر:', error);
}
}
يحمل الخطأ رمز الحالة، وجسم الاستجابة، والرابط — كل ما تحتاجه للتسجيل أو التنبيه أو الرسائل الموجهة للمستخدم. استورد هذا مرة واحدة، واستخدمه في كل مكان.
AbortController والمهلات الزمنية: النمط الإنتاجي لـ Node.js Fetch API

من دون مهلة زمنية، يظل طلب fetch معلقًا بلا نهاية عندما يصمت الخادم البعيد. يتعطل مسار Express لديك. وتلتهم Lambda ميزانية التنفيذ. ويبقى سكربتك مجرد... واقفًا مكانه.
راجعت نتائج البحث الأولى: لم أجد أي شرح خاص بـ Node.js يغطي إلغاء الطلب أو المهلات الزمنية. ومع ذلك، فإن المهلات الزمنية من أهم الأسباب التي تدفع المطورين إلى التمسك بـ Axios أو Got. وأحد مواضيع Reddit عنوانه حرفيًا «Node fetch لا تنتهي بمهلة».
استخدام AbortSignal.timeout() (في Node 18.11+)
أبسط طريقة — خيار إضافي واحد:
try {
const response = await fetch('https://api.example.com/data', {
signal: AbortSignal.timeout(5000), // 5 ثوانٍ
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
console.log(data);
} catch (error) {
if (error.name === 'TimeoutError') {
console.error('انتهت مهلة الطلب بعد 5 ثوانٍ.');
} else {
throw error;
}
}
ملاحظة: AbortSignal.timeout() يرمي TimeoutError، وليس AbortError. وهذه تفصيلة يخطئ فيها حتى بعض المطورين ذوي الخبرة.
مهلة يدوية باستخدام AbortController
لمزيد من التحكم — أو إذا كنت بحاجة إلى إلغاء الطلب بناءً على إجراء المستخدم لا على مؤقت فقط:
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch('https://api.example.com/data', {
signal: controller.signal,
});
const data = await response.json();
console.log(data);
} catch (error) {
if (error.name === 'AbortError') {
console.error('أُلغي الطلب يدويًا.');
} else {
throw error;
}
} finally {
clearTimeout(timeout);
}
التعامل مع AbortError مقابل TimeoutError
هذا التفريق مهم للتسجيل والرسائل الموجهة للمستخدم:
| مسار الإلغاء | اسم الخطأ في كتلة catch |
|---|---|
AbortSignal.timeout(ms) | TimeoutError |
controller.abort() | AbortError |
| فشل DNS/الشبكة | غالبًا TypeError: fetch failed |
إليك سيناريو عمليًا — مسار Express يستدعي API خارجيًا ويجب أن يرد خلال 3 ثوانٍ:
app.get('/dashboard', async (req, res, next) => {
try {
const data = await fetchJSON('https://api.example.com/report', {
signal: AbortSignal.timeout(3000),
});
res.json(data);
} catch (error) {
if (error.name === 'TimeoutError') {
res.status(504).json({ error: 'انتهت مهلة الـ API المصدرية' });
return;
}
next(error);
}
});
من دون هذا النمط، سيؤدي بطء الـ API المصدرية إلى حظر المسار بالكامل حتى يستسلم العميل.
منطق إعادة المحاولة وإعادة استخدام الاتصال: كيف تجعل Node.js Fetch API جاهزة للإنتاج
لا تحتوي fetch الأصلية على إعادة محاولة مدمجة. أي تعثر عابر في الشبكة أو استجابة 503 مؤقتة يعني ببساطة فشل الطلب. وفي معظم عمليات القراءة في الإنتاج، هذا غير مقبول.
غلاف قابل للتأليف مع تدرج زمني أسي
هذا قصير عمدًا — نحو 10 أسطر من المنطق الفعلي:
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
export async function fetchWithRetry(url, options = {}, retries = 2) {
for (let attempt = 0; ; attempt++) {
try {
const response = await fetch(url, options);
if (response.ok || ![408, 429, 500, 502, 503, 504].includes(response.status)) {
return response;
}
if (attempt >= retries) return response;
} catch (error) {
if (attempt >= retries) throw error;
}
await wait(250 * 2 ** attempt); // 250ms، 500ms، 1000ms...
}
}
متى تعيد المحاولة ومتى لا تفعل
- أعد المحاولة: طلبات GET وHEAD القابلة للتكرار، والحالات العابرة (408 و429 و500 و502 و503 و504)، وتعثرات الشبكة المؤقتة.
- لا تُعد المحاولة: طلبات POST غير القابلة للتكرار التي تنشئ سجلات أو تسحب أموالًا أو تطلق آثارًا جانبية — إلا إذا كنت تستخدم مفاتيح idempotency.
- احترم Retry-After: في 429 (تحديد المعدل) و503 (الخدمة غير متاحة)، افحص رأس
Retry-Afterقبل التراجع الزمني.
إذا لم ترد بناء منطق إعادة المحاولة بنفسك، فإن Ky هو غلاف fetch خفيف يضيف إعادة المحاولة والمهلة وhooks وHTTPError جاهزة — من دون أي تبعيات.
إعادة استخدام الاتصال باستخدام Agent وPool في Undici
في الحلقات عالية الإنتاجية — مثل استخراج مئات الصفحات، أو استدعاء API دفعيًا، أو الاستطلاع الدوري لخدمة — فإن إعادة استخدام اتصالات TCP توفر وقتًا كبيرًا. فكل اتصال جديد يعني بحث DNS جديدًا، ومصافحة TCP جديدة، و(في HTTPS) تفاوض TLS جديدًا.
وبما أن fetch في Node تعمل عبر Undici، يمكنك تمرير dispatcher مخصص:
import { Agent } from 'undici';
const agent = new Agent({
keepAliveTimeout: 10_000,
keepAliveMaxTimeout: 60_000,
});
const response = await fetch('https://api.example.com/data', {
dispatcher: agent,
});
ولمزيد من التحكم مع أصل محدد:
import { Pool } from 'undici';
const pool = new Pool('https://api.example.com', { connections: 10 });
const response = await fetch('https://api.example.com/data', {
dispatcher: pool,
});
// عند الانتهاء:
await pool.close();
تُظهر مقارنات الأداء في README الخاص بـ Undici أن إعادة استخدام الاتصال والتجميع يمكن أن يحسنا الإنتاجية بشكل كبير — فقد سجل undici - dispatch نحو 22,234 طلبًا/ثانية مقابل نحو 5,904 طلبًا/ثانية لـ undici - fetch في اختبارهم المحلي. ستختلف الأرقام الواقعية، لكن الاتجاه واضح: إذا كنت تُجري الكثير من الطلبات إلى الأصل نفسه، فالتجميع مهم.
شيء آخر: استهلك أجسام الاستجابة أو ألغها دائمًا. الأجسام غير المستهلكة قد تسبب تسريبات موارد في البنى الداخلية لـ HTTP في Node.
بث الاستجابات مع Node.js Fetch API
تنزيل ملفات كبيرة، أو تدفقات JSON مجزأة، أو أحداث يرسلها الخادم، أو مخرجات نماذج اللغة — كلها حالات يكون فيها انتظار الاستجابة كاملة قبل المعالجة مضيعة للوقت والذاكرة. البث يسمح لك بالتعامل مع البيانات فور وصولها.

يتضمن Node 18+ ReadableStream المتوافق مع المتصفح. إليك كيف تبث استجابة JSON مفصولة بأسطر وتعالج كل سطر عند وصوله:
const response = await fetch('https://example.com/large-file.ndjson');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
let newlineIndex;
while ((newlineIndex = buffer.indexOf('\n')) >= 0) {
const line = buffer.slice(0, newlineIndex).trim();
buffer = buffer.slice(newlineIndex + 1);
if (line) {
const item = JSON.parse(line);
console.log('تمت المعالجة:', item.id);
}
}
}
ولبث نص أبسط (مثل تمرير مخرجات نموذج لغة إلى stdout):
const response = await fetch('https://example.com/stream');
const reader = response.body.getReader();
const decoder = new TextDecoder();
for (;;) {
const { value, done } = await reader.read();
if (done) break;
process.stdout.write(decoder.decode(value, { stream: true }));
}
البث هو أحد المجالات التي تتفوق فيها fetch الأصلية وGot. أما دعم البث في Axios فهو أكثر محدودية.
عندما تصل fetch() إلى حدودها: استخراج ويب منظم عبر واجهات API
في مرحلة ما، لم تعد fetch هي عنق الزجاجة. وتصبح المشكلة الحقيقية: «لدي HTML، فماذا بعد؟»

fetch هي عميل HTTP — تسترجع البايتات أو النص أو JSON أو HTML. لكنها لا تعرف ما هي بطاقة منتج، أو سعر، أو تقييم، أو جدول جهات اتصال. وللاستخراج المنظم من الويب، تبدو السلسلة الخام المعتادة هكذا:
fetch()لتنزيل HTML- Cheerio (أو ما شابه) لاختيار العناصر باستخدام محددات CSS
- منطق مخصص للتنقل بين الصفحات
- عرض JavaScript عندما تكون الصفحات من جهة العميل
- معالجة proxy/anti-bot/CAPTCHA
- صيانة المحددات كلما تغيّر تخطيط الموقع
إليك مثالًا نموذجيًا يجمع fetch + Cheerio — نحو 15 سطرًا لاستخراج عناوين المنتجات:
import * as cheerio from 'cheerio';
const response = await fetch('https://example-store.com/products');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const html = await response.text();
const $ = cheerio.load(html);
const products = $('.product-card')
.map((_, el) => ({
name: $(el).find('.product-title').text().trim(),
price: $(el).find('.price').text().trim(),
url: new URL($(el).find('a').attr('href'), response.url).href,
}))
.get();
console.log(products);
هذا يعمل مع الصفحات المستقرة ذات HTML المتوقع. لكنه يصبح هشًا بسرعة — المحتوى المعرّض عبر JavaScript، وتغير أسماء الأصناف، وتدابير anti-bot، والصفحات المتعددة كلها تزيد التعقيد.
واجهة Thunderbit المفتوحة: من HTML الخام إلى بيانات منظمة في طلب واحد
هنا يصبح نوع مختلف من الأدوات مفيدًا. في Thunderbit، بنينا طبقة API تتعامل مع الأجزاء المزعجة — عرض JavaScript، والحماية من anti-bot، وتغيّرات التخطيط — حتى تركز أنت على البيانات التي تريدها فعلًا.
Distill API (POST /distill): يحول أي رابط إلى Markdown نظيف. مفيد لإدخال البيانات إلى نماذج اللغة، أو بناء قواعد معرفة، أو تحليل المحتوى — من دون الحاجة إلى محلل HTML.
Extract API (POST /extract): عرّف JSON Schema يصف البيانات المنظمة التي تريدها (اسم المنتج، السعر، التقييم)، وستستخرجها الذكاء الاصطناعي. بلا محددات CSS، وبلا كسر عند تغيّر التخطيط.
إليك نفس مهمة استخراج المنتجات باستخدام Extract API في Thunderbit — عبر fetch الأصلية:
const response = await fetch('https://openapi.thunderbit.com/openapi/v1/extract', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.THUNDERBIT_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: 'https://example-store.com/products',
renderMode: 'basic',
schema: {
type: 'object',
properties: {
products: {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string', description: 'اسم المنتج' },
price: { type: 'string', description: 'سعر المنتج المعروض' },
rating: { type: 'number', description: 'متوسط تقييم العملاء' },
},
required: ['name', 'price'],
},
},
},
required: ['products'],
},
}),
});
if (!response.ok) throw new Error(`Thunderbit API: ${response.status}`);
const result = await response.json();
console.log(result.data);
المقارنة: نحو 15 سطرًا من fetch + Cheerio (إلى جانب محددات هشة) مقابل استدعاء API واحد يعيد JSON نظيفًا. ولأعمال الدُفعات، تدعم Thunderbit حتى 50 رابطًا في طلب batch extract واحد وحتى 100 رابط في طلب batch distill واحد.
Thunderbit ليس بديلًا عن fetch — fetch هي وسيلة النقل. Thunderbit هي طبقة الاستخراج التي تلجأ إليها عندما يصبح تحليل HTML الخام هو المشكلة الحقيقية. وإذا كنت مهتمًا بالتسعير، فإن الخطة المجانية تمنحك 600 وحدة API للتجربة، وتبدأ الخطط المدفوعة من 6 دولارات شهريًا. ويمكنك أيضًا الاطلاع على إضافة Thunderbit لـ Chrome للاستخراج من دون كود مباشرةً داخل المتصفح.
وللمزيد حول أساليب الاستخراج المنظم، تغطي أدلتنا حول أفضل أدوات استخراج البيانات، وكيفية إنشاء أداة web scraper، واستخراج البيانات من موقع إلى Excel سير العمل المحددة بالتفصيل.
مرجع سريع: ورقة غش Node.js Fetch API
هذا القسم يستحق أن تحفظه في المفضلة. عد إليه عندما تحتاج نمطًا تنسخه وتلصقه.
| النمط | المقتطف |
|---|---|
| GET أساسي | const res = await fetch(url); const data = await res.json(); |
| POST أساسي | await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); |
| فحص خطأ HTTP | if (!res.ok) throw new Error(\\HTTP ${res.status}\); |
| مهلة زمنية (بسيطة) | await fetch(url, { signal: AbortSignal.timeout(5000) }); |
| إلغاء يدوي | const c = new AbortController(); setTimeout(() => c.abort(), 5000); await fetch(url, { signal: c.signal }); |
| حالات إعادة المحاولة | أعد المحاولة في 408 و429 و500 و502 و503 و504. لا تُعد محاولة POST تلقائيًا. |
| غلاف JSON | استخدم fetchJSON() لفحص ok، وتحليل نوع المحتوى، ورمي HTTPError. |
| تجمع الاتصالات | import { Pool } from 'undici'; const pool = new Pool(origin, { connections: 10 }); fetch(url, { dispatcher: pool }); |
| تدفقات المقاطع | const reader = res.body.getReader(); loop over await reader.read() |
| الاستخراج المنظم | استخدم Thunderbit Extract API عندما يكون الهدف حقولًا من صفحة ويب، لا HTML الخام. |
الخلاصة وأهم النقاط
fetch الأصلية في Node.js جاهزة للإنتاج في 2026 — لا حاجة إلى node-fetch للمشاريع الجديدة، ولا حاجة إلى تبعية Axios افتراضيًا. لكن fetch() الخام وحدها ليست استراتيجية HTTP إنتاجية.
الأشياء الخمسة التي تتجاهلها معظم الشروح — والتي يغطيها هذا الدليل:
- فخ الأخطاء: لا ترمي
fetch()خطأ عند 4xx/5xx. افحصresponse.okدائمًا أو استخدم غلافًا مثلfetchJSON(). - المهلات الزمنية: استخدم
AbortSignal.timeout()للحالات البسيطة. يرميAbortSignal.timeout()خطأTimeoutError، بينما يرميcontroller.abort()يدويًا خطأAbortError. - منطق إعادة المحاولة: غير مدمج. أضف تدرجًا زمنيًا أسيًا للطلبات القابلة للتكرار والفشل العابر. أو استخدم Ky لإعادة المحاولة بأسلوب fetch مباشرة.
- إعادة استخدام الاتصال: في الحلقات عالية الإنتاجية، استخدم
AgentأوPoolمن Undici عبر خيارdispatcher. - الاستخراج المنظم: عندما تحتاج بيانات من صفحات ويب (وليس HTML الخام فقط)، فكّر في واجهة استخراج مثل Thunderbit بدلًا من صيانة محددات CSS الهشة.
مصفوفة القرار في جملة واحدة: استخدم fetch الأصلية لمعظم المشاريع، وAxios للـ interceptors، وGot لإعادة المحاولة المدمجة وHTTP/2، وKy عندما تريد fetch مع إعدادات افتراضية أفضل، وواجهة Thunderbit عندما تصبح نصوص scraping المعتمدة على fetch أصعب من أن تُصان.
جرّب Thunderbit لاستخراج البيانات المنظمة
جرّب الأنماط الواردة في هذا الدليل. وإذا أردت أن ترى كيف تتعامل Thunderbit مع الاستخراج المنظم، فإن الخطة المجانية مكان جيد للبدء — أو شاهد شرحًا عمليًا على قناة Thunderbit على YouTube.
جرّب Thunderbit لاستخراج بيانات الويب بالذكاء الاصطناعي Get Started Free
الأسئلة الشائعة
1. هل fetch مدمجة في Node.js أم أحتاج إلى تثبيتها؟
fetch مدمجة في Node.js 18 والإصدارات الأحدث — لا حاجة للتثبيت. أصبحت مستقرة في Node 21 ومدعومة بالكامل في Node 22 LTS وNode 24 LTS. بالنسبة للإصدارات الأقدم من Node، يمكنك استخدام حزمة npm المسماة node-fetch، لكن المشاريع الجديدة يجب أن تستهدف إصدار LTS مدعوم.
2. هل ترمي fetch خطأ عند استجابات 404 أو 500؟
لا. fetch ترفض الـ promise فقط عند الفشل على مستوى الشبكة (أخطاء DNS، انعدام الاتصال، الطلبات الملغاة). استجابات HTTP مثل 404 و403 و500 تنجح بشكل طبيعي مع response.ok === false. يجب أن تفحص response.ok أو response.status صراحةً — أو تستخدم غلافًا مثل دالة fetchJSON() الموضحة في هذا الدليل.
3. كيف أضيف مهلة زمنية إلى fetch في Node.js؟
أبسط طريقة هي AbortSignal.timeout(ms)، والمتاحة في Node 18.11+: await fetch(url, { signal: AbortSignal.timeout(5000) }). هذا يرمي TimeoutError إذا تجاوز الطلب 5 ثوانٍ. ولمزيد من التحكم، أنشئ AbortController يدويًا واستدعِ controller.abort() من setTimeout. التقط AbortError للنمط اليدوي وTimeoutError مع AbortSignal.timeout().
4. هل يمكنني استخدام fetch لاستخراج الويب في Node.js؟
نعم، لكن fetch تعيد HTML الخام فقط. ستحتاج إلى محلل مثل Cheerio لاستخراج عناصر محددة، بالإضافة إلى منطق مخصص للترقيم الصفحي والصفحات المعروضة عبر JavaScript وتدابير anti-bot. وللاستخراج المنظم على نطاق واسع — عندما تريد JSON نظيفًا يحوي أسماء المنتجات أو الأسعار أو معلومات الاتصال — فكّر في Thunderbit's Extract API، التي تستخدم الذكاء الاصطناعي لإرجاع بيانات منظمة من دون محددات CSS أو كود يعتمد على التخطيط.
5. هل ينبغي أن أنتقل من Axios إلى fetch الأصلية في 2026؟
في المشاريع الجديدة على Node 22+، تعد fetch الأصلية خيارًا افتراضيًا قويًا. فهي بلا تبعيات، وتعتمد على Promise، وتشارك نفس الواجهة مع fetch في المتصفح. أبقِ على Axios إذا كنت تعتمد على معترضات الطلب/الاستجابة، أو الرفض الافتراضي لأخطاء HTTP، أو تحتاج توافقًا رجعيًا مع إصدارات Node الأقدم. كلاهما خياران صحيحان — والقرار يعتمد على الميزات التي يستخدمها مشروعك فعلًا.
اعرف المزيد


