شرح Async و Await في JavaScript بأمثلة عملية
لغات البرمجة

شرح Async و Await في JavaScript بأمثلة عملية

افهم async/await في JavaScript من أول مرة — بدون callbacks وبدون سلاسل then. أمثلة حقيقية من جلب البيانات للتحكّم بالأخطاء.

م
مؤسس LahbabiGuide
4 دقائق قراءة
شارك:

المشكلة: JavaScript متزامن بطبعه

JavaScript يُنفّذ الكود سطراً بسطر. لكن بعض العمليات تأخذ وقتاً:

  • جلب بيانات من API
  • قراءة ملف
  • استعلام قاعدة بيانات

ما الحلّ؟ نُخبر JavaScript أن ينتظر النتيجة بدون أن يوقف البرنامج.

التطوّر: من Callbacks إلى Async/Await

Callbacks (الطريقة القديمة):

js
getUser(1, (err, user) => {
  if (err) return handleError(err);
  getPosts(user.id, (err, posts) => {
    if (err) return handleError(err);
    getComments(posts[0].id, (err, comments) => {
      // هذا يُسمّى "callback hell" 😱
    });
  });
});

Promises (تحسين):

js
getUser(1)
  .then((user) => getPosts(user.id))
  .then((posts) => getComments(posts[0].id))
  .then((comments) => console.log(comments))
  .catch(handleError);

async/await (الأنظف):

js
async function loadData() {
  try {
    const user = await getUser(1);
    const posts = await getPosts(user.id);
    const comments = await getComments(posts[0].id);
    console.log(comments);
  } catch (err) {
    handleError(err);
  }
}

الأخير أسهل قراءةً ويشبه الكود المتزامن.

قواعد async/await

  1. await يعمل فقط داخل دالة async (أو على أعلى مستوى في ES modules)
  2. await "ينتظر" نتيجة الـ Promise
  3. الدالة المُعلَّمة بـ async ترجع Promise دائماً
إعلان

مثال كامل: جلب بيانات من API

js
async function fetchUser(id) {
  const response = await fetch(`https://api.example.com/users/${id}`);

  if (!response.ok) {
    throw new Error(`خطأ ${response.status}`);
  }

  const user = await response.json();
  return user;
}

// الاستخدام
fetchUser(1)
  .then(user => console.log(user))
  .catch(err => console.error(err));

// أو بداخل async أخرى
async function main() {
  try {
    const user = await fetchUser(1);
    console.log(user);
  } catch (err) {
    console.error("فشل:", err.message);
  }
}

main();

Promise.all: التوازي لسرعة أفضل

إن لم تكن العمليات مترابطة، نفّذها بالتوازي:

js
// ❌ بطيء (3 طلبات متسلسلة)
async function loadAllSlow() {
  const users = await fetch("/api/users").then(r => r.json());
  const posts = await fetch("/api/posts").then(r => r.json());
  const tags = await fetch("/api/tags").then(r => r.json());
  return { users, posts, tags };
}

// ✅ سريع (3 طلبات متوازية)
async function loadAllFast() {
  const [users, posts, tags] = await Promise.all([
    fetch("/api/users").then(r => r.json()),
    fetch("/api/posts").then(r => r.json()),
    fetch("/api/tags").then(r => r.json()),
  ]);
  return { users, posts, tags };
}

لو كل طلب يأخذ ثانية، الأولى تأخذ 3 ثوانٍ والثانية ثانية واحدة.

معالجة الأخطاء

try/catch

js
async function safeFetch(url) {
  try {
    const res = await fetch(url);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return await res.json();
  } catch (err) {
    console.error("خطأ:", err.message);
    return null; // أو أعد رمي الخطأ إن أردت
  }
}

على مستوى Promise.all

إن فشل أيّ واحد، الكل يفشل:

js
try {
  const [a, b] = await Promise.all([fetchA(), fetchB()]);
} catch (err) {
  // فشل A أو B أو كلاهما
}

للسماح بفشل بعضها واستمرار الآخر، استخدم Promise.allSettled:

js
const results = await Promise.allSettled([fetchA(), fetchB()]);
results.forEach((r, i) => {
  if (r.status === "fulfilled") {
    console.log(`#${i}: نجح`, r.value);
  } else {
    console.log(`#${i}: فشل`, r.reason);
  }
});

مثال واقعي: نظام تسجيل دخول

js
async function login(email, password) {
  try {
    // 1. إرسال بيانات تسجيل الدخول
    const res = await fetch("/api/login", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email, password }),
    });

    if (!res.ok) {
      if (res.status === 401) throw new Error("بيانات خاطئة");
      throw new Error("خطأ في الخادم");
    }

    const { token } = await res.json();

    // 2. حفظ التوكن
    localStorage.setItem("token", token);

    // 3. جلب بيانات المستخدم
    const userRes = await fetch("/api/me", {
      headers: { Authorization: `Bearer ${token}` },
    });
    const user = await userRes.json();

    return user;
  } catch (err) {
    console.error("فشل تسجيل الدخول:", err.message);
    throw err;
  }
}

// الاستخدام في نموذج
document.getElementById("login-form").addEventListener("submit", async (e) => {
  e.preventDefault();
  const form = e.target;
  try {
    const user = await login(form.email.value, form.password.value);
    alert(`أهلاً ${user.name}`);
  } catch (err) {
    alert(err.message);
  }
});

أخطاء شائعة

نسيان await

js
// ❌ data هنا Promise، ليس القيمة الفعلية
async function broken() {
  const data = fetch("/api/x").then(r => r.json());
  console.log(data); // Promise {...}
}

// ✅
async function fixed() {
  const data = await fetch("/api/x").then(r => r.json());
  console.log(data); // القيمة الحقيقية
}

استخدام await خارج async

js
// خطأ
function bad() {
  const x = await fetch("/api"); // SyntaxError
}

// صحيح
async function good() {
  const x = await fetch("/api");
}

forEach لا يدعم await

js
// ❌ لا يعمل كما تتوقّع
items.forEach(async (item) => {
  await process(item); // forEach لا ينتظر
});
console.log("انتهى"); // يُطبع قبل انتهاء العمليات

// ✅ استخدم for...of
for (const item of items) {
  await process(item);
}
console.log("انتهى فعلاً");

الأسئلة الشائعة

ما الفرق بين async/await و Promises؟

الاثنان يمثّلان نفس المفهوم — async/await هو سكّر نحوي (syntactic sugar) فوق Promises. تحت الغطاء، الكود نفسه.

هل أستخدم async مع كل دالة؟

لا — فقط مع الدوال التي تحتوي await. إضافة async لدالة عادية يحوّل قيمتها إلى Promise دون حاجة.

هل async يعني "متعدّد الخيوط"؟

لا — JavaScript ما زال single-threaded. async/await يسمح لك بإدارة العمليات اللا-متزامنة بشكل أنيق، لكن التنفيذ يبقى خيطاً واحداً.

شارك:
المزيد من لغات البرمجة
اقرأ أيضاً

مقالات ذات صلة