JavaScript المتقدم (ES6+)
واجهة برمجة Fetch API
واجهة برمجة Fetch API
توفر Fetch API طريقة حديثة قائمة على Promises لإجراء طلبات HTTP في JavaScript. إنها أكثر قوة ومرونة من XMLHttpRequest القديم، وتعمل بشكل مثالي مع بناء جملة async/await.
مقدمة إلى Fetch API
تُرجع دالة fetch() Promise يتم حله إلى كائن Response. بناء الجملة الأساسي بسيط:
// طلب GET أساسي
fetch("https://api.example.com/users")
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error("Error:", error));
// مع async/await (مفضل)
async function getUsers() {
try {
const response = await fetch("https://api.example.com/users");
const data = await response.json();
console.log(data);
} catch (error) {
console.error("Error:", error);
}
}
مهم: Fetch API يرفض Promise فقط عند حدوث خطأ في الشبكة. أخطاء HTTP (مثل 404 أو 500) لا تسبب الرفض - يجب عليك التحقق من خاصية response.ok أو response.status يدوياً.
إجراء طلبات GET
تُستخدم طلبات GET لاسترجاع البيانات من الخادم:
// طلب GET بسيط
async function fetchUser(userId) {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
// تحقق من نجاح الطلب
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const user = await response.json();
return user;
} catch (error) {
console.error("فشل جلب المستخدم:", error);
throw error;
}
}
// طلب GET مع معاملات الاستعلام
async function searchUsers(query, page = 1) {
const params = new URLSearchParams({
q: query,
page: page,
limit: 10
});
const response = await fetch(`https://api.example.com/users?${params}`);
if (!response.ok) {
throw new Error("فشل البحث");
}
return await response.json();
}
// الاستخدام
const results = await searchUsers("john", 1);
console.log(results);
طلبات POST - إنشاء البيانات
ترسل طلبات POST البيانات لإنشاء موارد جديدة على الخادم:
async function createUser(userData) {
try {
const response = await fetch("https://api.example.com/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer your-token-here"
},
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const newUser = await response.json();
return newUser;
} catch (error) {
console.error("فشل إنشاء المستخدم:", error);
throw error;
}
}
// الاستخدام
const userData = {
name: "John Doe",
email: "john@example.com",
age: 30
};
const newUser = await createUser(userData);
console.log("تم إنشاء المستخدم:", newUser);
نصيحة: اضبط دائماً رأس Content-Type المناسب عند إرسال البيانات. لبيانات JSON، استخدم "application/json". لبيانات النماذج، استخدم "application/x-www-form-urlencoded" أو "multipart/form-data".
طلبات PUT و PATCH - تحديث البيانات
PUT يستبدل المورد بالكامل، بينما PATCH يحدث حقول محددة:
// PUT - استبدال المورد بالكامل
async function updateUser(userId, userData) {
const response = await fetch(`https://api.example.com/users/${userId}`, {
method: "PUT",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error("فشل التحديث");
}
return await response.json();
}
// PATCH - تحديث حقول محددة
async function updateUserEmail(userId, newEmail) {
const response = await fetch(`https://api.example.com/users/${userId}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ email: newEmail })
});
if (!response.ok) {
throw new Error("فشل تحديث البريد الإلكتروني");
}
return await response.json();
}
// الاستخدام
await updateUserEmail(123, "newemail@example.com");
طلبات DELETE - إزالة البيانات
تُزيل طلبات DELETE الموارد من الخادم:
async function deleteUser(userId) {
try {
const response = await fetch(`https://api.example.com/users/${userId}`, {
method: "DELETE",
headers: {
"Authorization": "Bearer your-token-here"
}
});
if (!response.ok) {
throw new Error(`فشل حذف المستخدم: ${response.status}`);
}
// بعض APIs ترجع بدون محتوى (حالة 204)
if (response.status === 204) {
return { success: true };
}
return await response.json();
} catch (error) {
console.error("فشل الحذف:", error);
throw error;
}
}
// الاستخدام مع التأكيد
async function deleteUserWithConfirmation(userId) {
if (confirm("هل أنت متأكد من حذف هذا المستخدم؟")) {
await deleteUser(userId);
console.log("تم حذف المستخدم بنجاح");
}
}
رؤوس الطلبات والخيارات
يقبل Fetch كائن خيارات بالعديد من إمكانيات التكوين:
async function advancedFetch() {
const response = await fetch("https://api.example.com/data", {
method: "POST",
// الرؤوس
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer token123",
"X-Custom-Header": "custom-value"
},
// بيانات الجسم
body: JSON.stringify({ key: "value" }),
// بيانات الاعتماد (الكوكيز)
credentials: "include", // "omit", "same-origin", أو "include"
// التحكم في التخزين المؤقت
cache: "no-cache", // "default", "reload", "no-cache", "force-cache"
// معالجة إعادة التوجيه
redirect: "follow", // "follow", "error", أو "manual"
// المُحيل
referrer: "client",
// فحص التكامل
integrity: "sha256-...",
// وضع الطلب
mode: "cors" // "cors", "no-cors", "same-origin"
});
return await response.json();
}
معالجة الاستجابة
يوفر كائن Response عدة طرق لقراءة جسم الاستجابة:
async function handleDifferentResponses() {
// استجابة JSON
const jsonResponse = await fetch("/api/users");
const jsonData = await jsonResponse.json();
// استجابة نصية
const textResponse = await fetch("/api/message");
const textData = await textResponse.text();
// استجابة Blob (للصور، الملفات)
const imageResponse = await fetch("/api/image.jpg");
const imageBlob = await imageResponse.blob();
const imageUrl = URL.createObjectURL(imageBlob);
// استجابة FormData
const formResponse = await fetch("/api/form");
const formData = await formResponse.formData();
// ArrayBuffer (للبيانات الثنائية)
const binaryResponse = await fetch("/api/binary");
const buffer = await binaryResponse.arrayBuffer();
// خصائص الاستجابة
console.log("Status:", jsonResponse.status); // 200, 404, إلخ
console.log("OK:", jsonResponse.ok); // true إذا 200-299
console.log("Headers:", jsonResponse.headers.get("Content-Type"));
console.log("URL:", jsonResponse.url);
}
مهم: يمكن قراءة جسم الاستجابة مرة واحدة فقط! إذا كنت بحاجة إلى قراءته عدة مرات، استنسخ الاستجابة أولاً:
const clone = response.clone();
معالجة الأخطاء مع Fetch
المعالجة الصحيحة للأخطاء أمر حاسم للتطبيقات القوية:
async function robustFetch(url, options = {}) {
try {
const response = await fetch(url, options);
// معالجة أخطاء HTTP
if (!response.ok) {
// حاول الحصول على تفاصيل الخطأ من الاستجابة
let errorMessage = `HTTP error! status: ${response.status}`;
try {
const errorData = await response.json();
errorMessage = errorData.message || errorMessage;
} catch {
// جسم الاستجابة ليس JSON
}
throw new Error(errorMessage);
}
return await response.json();
} catch (error) {
// أخطاء الشبكة، انتهاء المهلة، أو أخطاء مرمية
if (error.name === "TypeError") {
console.error("خطأ في الشبكة أو مشكلة CORS");
}
console.error("خطأ Fetch:", error.message);
throw error;
}
}
// مع انتهاء المهلة
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
if (error.name === "AbortError") {
throw new Error("انتهت مهلة الطلب");
}
throw error;
}
}
مثال من العالم الحقيقي: API CRUD كامل
// فئة مساعدة للـ API
class UserAPI {
constructor(baseUrl) {
this.baseUrl = baseUrl;
}
async request(endpoint, options = {}) {
const url = `${this.baseUrl}${endpoint}`;
const config = {
...options,
headers: {
"Content-Type": "application/json",
...options.headers
}
};
const response = await fetch(url, config);
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new Error(error.message || `HTTP error! status: ${response.status}`);
}
// معالجة 204 No Content
if (response.status === 204) {
return null;
}
return await response.json();
}
// GET جميع المستخدمين
async getAll() {
return await this.request("/users");
}
// GET مستخدم واحد
async getById(id) {
return await this.request(`/users/${id}`);
}
// POST إنشاء مستخدم
async create(userData) {
return await this.request("/users", {
method: "POST",
body: JSON.stringify(userData)
});
}
// PUT تحديث مستخدم
async update(id, userData) {
return await this.request(`/users/${id}`, {
method: "PUT",
body: JSON.stringify(userData)
});
}
// DELETE مستخدم
async delete(id) {
return await this.request(`/users/${id}`, {
method: "DELETE"
});
}
}
// الاستخدام
const api = new UserAPI("https://api.example.com");
try {
const users = await api.getAll();
const user = await api.getById(1);
const newUser = await api.create({ name: "John", email: "john@example.com" });
await api.update(1, { name: "Jane" });
await api.delete(1);
} catch (error) {
console.error("خطأ API:", error.message);
}
تمرين تطبيقي:
المهمة: أنشئ دالة تجلب المنشورات من API وتتعامل مع جميع الأخطاء المحتملة بشكل سلس.
// مهمتك: تنفيذ هذه الدالة
async function fetchPostsWithRetry(url, maxRetries = 3) {
// 1. حاول جلب المنشورات
// 2. إذا فشل، أعد المحاولة حتى maxRetries مرات
// 3. تعامل مع أخطاء الشبكة وأخطاء HTTP وانتهاء المهلة
// 4. أرجع المنشورات أو ارمي خطأ وصفي
}
الحل:
async function fetchPostsWithRetry(url, maxRetries = 3) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
console.log(`المحاولة ${attempt} من ${maxRetries}`);
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
const response = await fetch(url, {
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const posts = await response.json();
console.log(`نجح! تم جلب ${posts.length} منشور`);
return posts;
} catch (error) {
lastError = error;
if (error.name === "AbortError") {
console.warn(`المحاولة ${attempt}: انتهت مهلة الطلب`);
} else {
console.warn(`المحاولة ${attempt}: ${error.message}`);
}
// لا تنتظر بعد آخر محاولة
if (attempt < maxRetries) {
const delay = attempt * 1000; // تأخير متزايد
console.log(`إعادة المحاولة في ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw new Error(`فشل بعد ${maxRetries} محاولات: ${lastError.message}`);
}
الملخص
في هذا الدرس، تعلمت:
- Fetch API توفر طلبات HTTP حديثة قائمة على Promises
- طرق GET و POST و PUT و PATCH و DELETE لعمليات مختلفة
- رؤوس الطلبات والخيارات للتخصيص
- معالجة الاستجابة بـ .json() و .text() و .blob() وغيرها
- المعالجة الصحيحة للأخطاء HTTP وأخطاء الشبكة
- Fetch يرفض فقط على أخطاء الشبكة، وليس أخطاء HTTP
- بناء فئات مساعدة API قابلة لإعادة الاستخدام
التالي: في الدرس التالي، سنتعمق أكثر في العمل مع بيانات JSON، بما في ذلك التحليل والتحويل إلى سلسلة نصية ومعالجة الحالات الحرجة!