طلبات API متتابعة بالترتيب
درس 9 من 11
9طلبات API متتابعة بالترتيب
الصورة الكبيرةترسل عشرة طلبات للشبكة، فيعود أسرعها أولًا لا أولها. إن طبعت كل رد لحظة وصوله يختلط الترتيب. الحل أن تفصل «متى وصل» عن «أين يُعرض».
ماذا تشاهد في الرسم: خط الطلبات: الطلب الأول يعطي قائمة روابط، ثم يُطلب كل رابط، والترتيب يُحفظ بموقع الرابط لا بوقت وصول رده.
خارطة هذا الجزء
المفاهيم بالترتيب، اضغط أيًّا منها للقفز إليه:
طريقان إلى نفس الفكرة
ترسل خمسة أصدقاء لخمسة محلات بقائمة مرتبة. يعودون بأوقات مختلفة. لو كتبت ما يحضره كل واحد لحظة رجوعه تختلط القائمة. الأصح أن تضع كل شيء في خانته حسب رقم المحل، ثم تقرأ الخانات بالترتيب.
fetch يعيد Promise. .map(url => fetch(url)) يطلق كل الطلبات معًا، وPromise.all ينتظرها ويعيد النتائج بترتيب المدخلات مهما كان وقت الوصول، ويفشل كله عند أول رفض (Promise.allSettled لا يفشل). for (const u of urls) await fetch(u) متتابع: أبطأ لكنه يحترم حدود المعدّل. forEach مع async لا ينتظر شيئًا، وهو خطأ شائع. للتحكّم بالتزامن استخدم مجموعة عمّال أو مكتبة مثل p-limit.
قبل أن تبدأ
📨HTTP وJSON
تعرف الطلب والرد وشكل JSON.
🟨JavaScript الحديثة
درس الواجهات الأمامية: Promises وasync/await.
الفكرة الجوهرية
1لماذا ينقلب الترتيب
هنا نحاكي الشبكة بمؤقّتات بتأخيرات مختلفة، فلا نحتاج اتصالًا. الطلب الثالث أسرع فيطبع أولًا:
const delays = { a: 30, b: 20, c: 5 };
const fakeFetch = (id) =>
new Promise((resolve) => setTimeout(() => resolve(`item ${id}`), delays[id]));
for (const id of ["a", "b", "c"]) {
fakeFetch(id).then((item) => console.log("arrived:", item));
}2Promise.all يحفظ الترتيب
نطلق كل الطلبات معًا، وننتظرها كلها، فتأتي النتائج مرتّبة حسب المدخلات:
const delays = { a: 30, b: 20, c: 5 };
const fakeFetch = (id) =>
new Promise((resolve) => setTimeout(() => resolve(`item ${id}`), delays[id]));
async function main() {
const items = await Promise.all(["a", "b", "c"].map(fakeFetch));
items.forEach((item) => console.log(item));
for (const id of ["c", "b", "a"]) {
console.log("sequential:", await fakeFetch(id));
}
}
main();ids.forEach(async (id) => console.log(await fetch(id))) يبدو متتابعًا لكنه لا ينتظر شيئًا: forEach يتجاهل الـ Promise المعاد. استخدم for...of أو Promise.all.3تمرين: واجهة عامة حقيقية
اختر واجهة عامة مجانية بلا مفتاح تعيد كائنًا فيه قائمة روابط (مثلًا فيلم وقائمة روابط شخصياته)، واكتب سكربت Node يأخذ رقمًا من سطر الأوامر، يجلب الكائن، ثم يطبع اسم كل عنصر بنفس ترتيب القائمة. استخدم fetch المدمج في Node 18+ وprocess.argv[2]، وتعامل مع رد غير 200.
// skeleton: fill in the two TODOs
const id = process.argv[2];
const res = await fetch(`${process.env.API_BASE}/films/${id}/`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const film = await res.json();
// TODO: fetch every link in film.characters, keeping the order
// TODO: print each name on its own line.mjs حتى يعمل await في المستوى الأعلى.مثال محلول
المسألة: 50 رابطًا، والواجهة تسمح بـ 10 طلبات بالثانية. التحليل: Promise.all على الخمسين يتجاوز الحد فتأتي أخطاء 429، والمتتابع بطيء. الحل: قسّم الروابط لدفعات من 10، وawait Promise.all(batch) لكل دفعة، ثم اجمع النتائج بالترتيب. سرعة معقولة وترتيب محفوظ واحترام للحد.
خرافة مقابل حقيقة
| الخرافة | الحقيقة |
|---|---|
| Promise.all يعيد النتائج بترتيب الوصول. | يعيدها بترتيب المدخلات دائمًا. |
| async داخل forEach ينتظر كل عنصر. | forEach لا ينتظر الـ Promise. استخدم for...of. |
اختبر فهمك
بأي ترتيب يعيد Promise.all نتائجه؟
النتيجة i تخص المدخل i مهما كان وقت وصولها.
أي حلقة تنتظر كل await بالتتابع؟
for...of داخل دالة async يوقف الحلقة عند كل await.
رابط واحد فشل في Promise.all. ماذا يحدث؟
لو أردت النتائج الناجحة مع الفاشلة استخدم Promise.allSettled.
بطاقات مراجعة سريعة
تحقق من فهمك
ما الفرق في الزمن بين المتوازي والمتتابع؟
المتوازي يأخذ زمن أبطأ طلب، والمتتابع مجموع كل الأزمنة.
لماذا لا يكفي console.log داخل then؟
لأنه يطبع بترتيب الوصول لا بترتيب القائمة.
كيف تحترم حد المعدّل؟
بدفعات محدودة الحجم أو مجموعة عمّال بعدد ثابت.
مصادر هذا الدرس
مرتّبة بالنوع. ابدأ بالتوثيق الرسمي إذا تبي المرجع الدقيق، وبالمقال أو الفيديو إذا تبي الشرح أولًا.
خلّصت الدرس؟
سجّل الدخول ليُحفظ تقدّمك.