تخطَّ إلى المحتوى

جلب البيانات من الإنترنت في JavaScript: fetch

الدرس 24 من 28 · JavaScript

حتى الآن كانت كل بياناتك مكتوبة داخل الكود نفسه. لكن التطبيقات الحقيقية تجلب بياناتها من خادم (Server): حالة الطقس، وقائمة المنتجات، ورسائل الأصدقاء. في هذا الدرس ستستخدم الدالة fetch لتطلب بيانات من الإنترنت وتعرضها، معتمداً على ما تعلمته عن الوعود و JSON.

الـ API هو الباب الذي يفتحه الخادم للبرامج: ترسل طلباً إلى عنوان محدد، فيرد عليك ببيانات، وغالباً تكون بصيغة JSON.

تخيّل النادل في المطعم: أنت لا تدخل المطبخ، بل تطلب من النادل ما تريد، فيذهب ويعود بطبقك. الـ API هو هذا النادل بين صفحتك وبين الخادم.

سنتدرب على خادم تجريبي مجاني اسمه dummyjson.com، يعطيك بيانات وهمية جاهزة للتجربة. والعنوان التالي مثلاً يُرجع المهمة رقم 1:

https://dummyjson.com/todos/1

افتحه في متصفحك وسترى نص JSON. والآن سنطلبه بالكود.

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

لاحظ أن هناك انتظارين:

  1. await fetch(العنوان): ترسل الطلب وتنتظر حتى يبدأ رد الخادم بالوصول، وتعطيك كائن الاستجابة (Response).
  2. await response.json(): تنتظر اكتمال وصول المحتوى، ثم تحوّله من نص JSON إلى كائن JavaScript. أي أنها تقوم بعمل JSON.parse بدلاً منك.

قبل أن تقرأ المحتوى، يخبرك كائن الاستجابة هل نجح الطلب:

async function checkStatus() {
const response = await fetch("https://dummyjson.com/todos/1");
console.log(response.status);
console.log(response.ok);
}
checkStatus();
الناتج
200
true

status هو رمز الحالة، وهو رقم يلخص به الخادم ما حدث:

الرمز معناه
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();
الناتج
404
false
Todo 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، سواء كان انقطاعاً في الشبكة أو رداً برمز خطأ.

العنوان 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) لترى ما بداخله.

الطلبات السابقة من نوع 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 وبالمهمة بعد أن أعطاها رقماً جديداً.

اضغط «شغّل»، ثم اضغط زر «حمّل المهام» في صندوق النتيجة:

جرّب بنفسكJavaScript
أو اضغط Ctrl + Enter

الناتج

هذا هو الشكل الذي ستكرره في كل تطبيق: رسالة «جارٍ التحميل»، ثم عرض البيانات عند النجاح أو رسالة واضحة عند الفشل، و finally تعيد الزر للعمل في الحالتين. والسطر button.disabled = true يعطّل الزر مؤقتاً حتى لا يُرسَل الطلب مرتين.

جرّب الآن:

  1. غيّر limit=5 إلى limit=10.
  2. غيّر todos في العنوان إلى todoss وشاهد الرسالة التي تظهر.
  3. اعرض المهام غير المنتهية فقط باستخدام filter.
  • نسيان await قبل response.json(): هذه الدالة تُرجع وعداً هي أيضاً. بدون await تحصل على وعد معلّق بدلاً من البيانات.
  • الاعتماد على catch وحدها: الرد برمز 404 أو 500 لا يصل إلى catch. افحص response.ok دائماً.
  • إرسال الكائن مباشرة في body: الخادم يستقبل النص [object Object]. حوّل الكائن بـ JSON.stringify وأضف ترويسة Content-Type.

سؤال

طلبت عنواناً غير موجود فردّ الخادم بالرمز 404. ماذا يحدث في الكود؟

سؤال

لماذا نكتب await مرتين عند جلب بيانات JSON؟

  • fetch(العنوان) ترسل طلباً وتُرجع وعداً بكائن الاستجابة.
  • await response.json() تقرأ المحتوى وتحوّله إلى كائن JavaScript.
  • fetch لا ترمي خطأ عند 404 أو 500، فافحص response.ok وارمِ الخطأ بنفسك.
  • لإرسال بيانات: method: "POST" مع headers و body محوّل بـ JSON.stringify.
  • ضع كل طلب داخل try و catch، واعرض للمستخدم حالة التحميل وحالة الفشل.