جلب البيانات من الإنترنت في JavaScript: fetch
حتى الآن كانت كل بياناتك مكتوبة داخل الكود نفسه. لكن التطبيقات الحقيقية تجلب بياناتها من خادم (Server): حالة الطقس، وقائمة المنتجات، ورسائل الأصدقاء. في هذا الدرس ستستخدم الدالة fetch لتطلب بيانات من الإنترنت وتعرضها، معتمداً على ما تعلمته عن الوعود و JSON.
ما هو الـ API؟
Section titled “ما هو الـ API؟”الـ API هو الباب الذي يفتحه الخادم للبرامج: ترسل طلباً إلى عنوان محدد، فيرد عليك ببيانات، وغالباً تكون بصيغة JSON.
تخيّل النادل في المطعم: أنت لا تدخل المطبخ، بل تطلب من النادل ما تريد، فيذهب ويعود بطبقك. الـ API هو هذا النادل بين صفحتك وبين الخادم.
سنتدرب على خادم تجريبي مجاني اسمه dummyjson.com، يعطيك بيانات وهمية جاهزة للتجربة. والعنوان التالي مثلاً يُرجع المهمة رقم 1:
https://dummyjson.com/todos/1افتحه في متصفحك وسترى نص JSON. والآن سنطلبه بالكود.
أول طلب
Section titled “أول طلب”async function loadTodo() { const response = await fetch("https://dummyjson.com/todos/1"); const todo = await response.json();
console.log("المهمة:", todo.todo); console.log("منتهية؟", todo.completed);}
loadTodo();المهمة: Do something nice for someone you care aboutمنتهية؟ falseلاحظ أن هناك انتظارين:
await fetch(العنوان): ترسل الطلب وتنتظر حتى يبدأ رد الخادم بالوصول، وتعطيك كائن الاستجابة (Response).await response.json(): تنتظر اكتمال وصول المحتوى، ثم تحوّله من نص JSON إلى كائن JavaScript. أي أنها تقوم بعملJSON.parseبدلاً منك.
كائن الاستجابة: status و ok
Section titled “كائن الاستجابة: status و ok”قبل أن تقرأ المحتوى، يخبرك كائن الاستجابة هل نجح الطلب:
async function checkStatus() { const response = await fetch("https://dummyjson.com/todos/1"); console.log(response.status); console.log(response.ok);}
checkStatus();200truestatus هو رمز الحالة، وهو رقم يلخص به الخادم ما حدث:
| الرمز | معناه |
|---|---|
200 |
نجح الطلب |
201 |
نجح وتم إنشاء شيء جديد |
404 |
العنوان المطلوب غير موجود |
500 |
حدث خطأ داخل الخادم |
أما ok فتكون true إذا كان الرمز بين 200 و 299، أي أن الطلب نجح.
فخ مشهور: 404 ليس خطأ عند fetch
Section titled “فخ مشهور: 404 ليس خطأ عند fetch”ماذا لو طلبت مهمة غير موجودة؟
async function loadMissing() { const response = await fetch("https://dummyjson.com/todos/99999"); console.log(response.status); console.log(response.ok);
const data = await response.json(); console.log(data.message);}
loadMissing();404falseTodo with id '99999' not foundلم يُرمَ أي خطأ! من وجهة نظر fetch، الخادم ردّ، فالمهمة تمت. هي ترفض الوعد فقط عندما لا يصل رد أصلاً: انقطع الإنترنت، أو العنوان لا يشير إلى خادم موجود.
لذلك افحص response.ok بنفسك، وارمِ الخطأ بيدك:
async function getTodo(id) { const response = await fetch(`https://dummyjson.com/todos/${id}`); if (!response.ok) { throw new Error(`فشل الطلب: ${response.status}`); } return response.json();}
async function show(id) { try { const todo = await getTodo(id); console.log("وصلت:", todo.todo); } catch (error) { console.log(error.message); }}
async function main() { await show(2); await show(99999);}
main();وصلت: Memorize a poemفشل الطلب: 404الآن يمر كل فشل من مكان واحد هو catch، سواء كان انقطاعاً في الشبكة أو رداً برمز خطأ.
جلب قائمة
Section titled “جلب قائمة”العنوان todos بدون رقم يُرجع قائمة. ونضيف ?limit=3 في آخره لنطلب أول ثلاث مهام فقط:
async function loadList() { const response = await fetch("https://dummyjson.com/todos?limit=3"); const data = await response.json();
console.log("إجمالي المهام:", data.total); data.todos.forEach((item) => { const mark = item.completed ? "✓" : "○"; console.log(mark, item.todo); });}
loadList();إجمالي المهام: 254○ Do something nice for someone you care about✓ Memorize a poem✓ Watch a classic movieهذا الخادم يضع المصفوفة داخل خاصية اسمها todos، وخادم آخر قد يُرجع المصفوفة مباشرة. لكل API شكله، ولذلك تقرأ توثيقه أولاً، أو تطبع الرد كاملاً بـ console.log(data) لترى ما بداخله.
إرسال بيانات: POST
Section titled “إرسال بيانات: POST”الطلبات السابقة من نوع GET، أي «أعطني بيانات». ولكي ترسل بيانات جديدة إلى الخادم تستخدم POST، وتمرر إلى fetch كائناً ثانياً فيه الإعدادات:
async function addTodo() { const response = await fetch("https://dummyjson.com/todos/add", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ todo: "تعلّم fetch", completed: false, userId: 5 }), });
const saved = await response.json(); console.log(response.status); console.log(saved);}
addTodo();201{ id: 255, todo: 'تعلّم fetch', completed: false, userId: 5 }method: نوع الطلب.headers: معلومات عن الطلب. هنا نخبر الخادم أن المحتوى بصيغة JSON.body: البيانات نفسها. ويجب أن تكون نصاً، ولذلك نحوّل الكائن بـJSON.stringify.
ردّ الخادم بالرمز 201 وبالمهمة بعد أن أعطاها رقماً جديداً.
جرّب بنفسك
Section titled “جرّب بنفسك”اضغط «شغّل»، ثم اضغط زر «حمّل المهام» في صندوق النتيجة:
الناتج
هذا هو الشكل الذي ستكرره في كل تطبيق: رسالة «جارٍ التحميل»، ثم عرض البيانات عند النجاح أو رسالة واضحة عند الفشل، و finally تعيد الزر للعمل في الحالتين. والسطر button.disabled = true يعطّل الزر مؤقتاً حتى لا يُرسَل الطلب مرتين.
جرّب الآن:
- غيّر
limit=5إلىlimit=10. - غيّر
todosفي العنوان إلىtodossوشاهد الرسالة التي تظهر. - اعرض المهام غير المنتهية فقط باستخدام
filter.
أخطاء شائعة
Section titled “أخطاء شائعة”- نسيان
awaitقبلresponse.json(): هذه الدالة تُرجع وعداً هي أيضاً. بدونawaitتحصل على وعد معلّق بدلاً من البيانات. - الاعتماد على
catchوحدها: الرد برمز 404 أو 500 لا يصل إلىcatch. افحصresponse.okدائماً. - إرسال الكائن مباشرة في
body: الخادم يستقبل النص[object Object]. حوّل الكائن بـJSON.stringifyوأضف ترويسةContent-Type.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
طلبت عنواناً غير موجود فردّ الخادم بالرمز 404. ماذا يحدث في الكود؟
fetch ترفض الوعد عند فشل الاتصال نفسه فقط. أما رموز الخطأ مثل 404 فتصلك كاستجابة عادية فيها ok تساوي false.
سؤال
لماذا نكتب await مرتين عند جلب بيانات JSON؟
fetch تُرجع وعداً بكائن الاستجابة، و response.json() تُرجع وعداً آخر بالبيانات بعد قراءتها وتحويلها.
الخلاصة
Section titled “الخلاصة”fetch(العنوان)ترسل طلباً وتُرجع وعداً بكائن الاستجابة.await response.json()تقرأ المحتوى وتحوّله إلى كائن JavaScript.fetchلا ترمي خطأ عند 404 أو 500، فافحصresponse.okوارمِ الخطأ بنفسك.- لإرسال بيانات:
method: "POST"معheadersوbodyمحوّل بـJSON.stringify. - ضع كل طلب داخل
tryوcatch، واعرض للمستخدم حالة التحميل وحالة الفشل.