404 Not Found

404 Not Found


nginx

البرمجة غير المتزامنة في JavaScript

JavaScript أحادي الخيط — يمكنه فعل شيء واحد فقط في كل مرة. لكن إذا استغرق طلب الشبكة 3 ثواني، هل يجب أن تتجمد الصفحة لـ 3 ثواني؟ بالطبع لا. البرمجة غير المتزامنة تحل هذا: بدلاً من الانتظار، تأخذ رقماً وتعود عندما يكون جاهزاً. مثل نظام طابور المطعم — لا تقف عند باب المطبخ، بل تجلس وتنتظر استدعاء رقمك.


متزامن مقابل غير متزامن

متزامن: الكود ينفذ سطر بسطر — السطر التالي ينتظر انتهاء الحالي. مثل الوقوف في الصف — تنتظر حتى ينتهي الشخص أمامك.

غير متزامن: عمليات معينة (طلبات الشبكة، المؤقتات، إدخال/إخراج الملفات) تبدأ دون عرض الكود اللاحق. عندما تنتهي، يتم إخطارك عبر استدعاء أو Promise.

HTML
<script>
console.log('1');
setTimeout(function() {
  console.log('2');
}, 1000);
console.log('3');
</script>

ترتيب الإخراج هو 1 → 3 → 2. setTimeout غير متزامن — الاستدعاء ينفذ بعد ثانية واحدة دون عرض console.log('3').


الاستدعاءات (Callbacks)

الاستدعاءات هي النمط الأساسي للبرمجة غير المتزامنة — تمرير دالة كمعامل، وتُستدعى عندما تكتمل العملية غير المتزامنة.

مثال: استخدام أساسي للاستدعاء

HTML
<div id="output" style="padding: 10px; border: 1px solid #ccc;"></div>

<script>
const output = document.getElementById('output');

function fetchData(callback) {
  output.textContent = 'جاري التحميل...';
  setTimeout(function() {
    callback('تم تحميل البيانات!');
  }, 2000);
}

fetchData(function(data) {
  output.textContent = data;
});
</script>
▶ جرّب الكود

جحيم الاستدعاءات (Callback Hell)

عندما تعتمد العمليات غير المتزامنة على بعضها تسلسلياً، تتداخل الاستدعاءات أعمق وأعمق — هذا هو "جحيم الاستدعاءات" سيئ السمعة.

HTML
<script>
getUser(function(user) {
  getOrders(user.id, function(orders) {
    getOrderDetail(orders[0].id, function(detail) {
      getItems(detail.itemId, function(items) {
        console.log('4 مستويات عميقة — أضف تعقيداً أكثر ويصبح غير مقروء');
      });
    });
  });
});
</script>
💡 جحيم الاستدعاءات يبدو كهرم أفقي — يصعب قراءته وصيانته. اخترع Promises لحل هذا.


Promise

Promise هو كائن يمثل النتيجة النهائية لعملية غير متزامنة. لديه ثلاث حالات:

الحالة الوصف
pending قيد التنفيذ، لا نتيجة بعد
fulfilled اكتملت بنجاح، النتيجة متاحة
rejected فشلت، الخطأ متاح

بمجرد انتقال Promise من pending إلى fulfilled أو rejected، لا تتغير مرة أخرى — مثل سهم أُطلق، لا يمكن استدعاؤه.

إنشاء Promise

HTML
<script>
const promise = new Promise(function(resolve, reject) {
  // عملية غير متزامنة
  // استدعاء resolve(result) عند النجاح
  // استدعاء reject(error) عند الفشل
});
</script>

then / catch / finally

HTML
<script>
promise
  .then(function(result) { console.log(result); })   // ينفذ عند النجاح
  .catch(function(error) { console.error(error); })    // ينفذ عند الفشل
  .finally(function() { console.log('تم'); });         // ينفذ بغض النظر
</script>

مثال: محاكاة طلب بـ Promise

HTML
<button id="loadBtn">تحميل البيانات</button>
<div id="output" style="padding: 10px; border: 1px solid #ccc; margin-top: 10px;"></div>

<script>
const btn = document.getElementById('loadBtn');
const output = document.getElementById('output');

function fetchUser() {
  return new Promise(function(resolve, reject) {
    output.textContent = 'جاري التحميل...';
    setTimeout(function() {
      const success = true;
      if (success) {
        resolve({ name: 'أليس', age: 25 });
      } else {
        reject(new Error('فشل التحميل'));
      }
    }, 1500);
  });
}

btn.addEventListener('click', function() {
  fetchUser()
    .then(function(user) {
      output.textContent = 'الاسم: ' + user.name + '، العمر: ' + user.age;
    })
    .catch(function(err) {
      output.textContent = 'خطأ: ' + err.message;
    })
    .finally(function() {
      console.log('اكتمل الطلب (نجاح أو فشل)');
    });
});
</script>
▶ جرّب الكود

التسلسل

then نفسها تُرجع Promise، لذا يمكنك تسلسل الاستدعاءات — جحيم الاستدعاءات يصبح خطاً مباشراً.

HTML
<script>
fetchUser()
  .then(function(user) { return fetchOrders(user.id); })
  .then(function(orders) { return fetchDetail(orders[0].id); })
  .then(function(detail) { console.log(detail); })
  .catch(function(err) { console.error(err); });
</script>
💡 catch واحدة في نهاية التسلسل تلتقط الأخطاء من أي خطوة — لا تحتاج catch بعد كل then.


async / await

async/await هو سكر صناعي فوق Promises — يجعل الكود غير المتزامن يبدو متزامناً، مما يحسن القراءة بشكل كبير.

مثال: إعادة كتابة الطلب أعلاه بـ async/await

HTML
<button id="loadBtn">تحميل البيانات</button>
<div id="output" style="padding: 10px; border: 1px solid #ccc; margin-top: 10px;"></div>

<script>
const btn = document.getElementById('loadBtn');
const output = document.getElementById('output');

function fetchUser() {
  return new Promise(function(resolve) {
    setTimeout(function() {
      resolve({ name: 'بوب', age: 30 });
    }, 1500);
  });
}

btn.addEventListener('click', async function() {
  try {
    output.textContent = 'جاري التحميل...';
    const user = await fetchUser();
    output.textContent = 'الاسم: ' + user.name + '، العمر: ' + user.age;
  } catch (err) {
    output.textContent = 'خطأ: ' + err.message;
  } finally {
    console.log('اكتمل الطلب');
  }
});
</script>
▶ جرّب الكود
💡 مقارنة بالاستدعاءات، async/await يحول "خذ رقماً وانتظر" إلى "ابقَ في الصف دون مغادرة" — تدفق الكود مباشقر والقراءة ترتفع بشكل كبير.

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

async/await يستخدم try/catch لمعالجة الأخطاء — نفس النمط المستخدم مع الكود المتزامن.

HTML
<script>
async function loadAll() {
  try {
    const user = await fetchUser();
    const orders = await fetchOrders(user.id);
    console.log(orders);
  } catch (err) {
    console.error('حدث خطأ ما:', err);
  }
}
</script>

Promise.all و Promise.race

الطريقة الوصف
Promise.all([p1, p2, p3]) ينجح فقط إذا نجح الجميع؛ يفشل بمجرد فشل واحد
Promise.race([p1, p2, p3]) يستخدم نتيجة أيهما يحل أولاً (نجاح أو فشل)

مثال: طلبات متزامنة مع Promise.all

HTML
<button id="loadAll">تحميل متزامن</button>
<div id="output" style="padding: 10px; border: 1px solid #ccc; margin-top: 10px;"></div>

<script>
const btn = document.getElementById('loadAll');
const output = document.getElementById('output');

function delay(ms, value) {
  return new Promise(function(resolve) {
    setTimeout(function() { resolve(value); }, ms);
  });
}

btn.addEventListener('click', async function() {
  output.textContent = 'جاري التحميل...';

  const results = await Promise.all([
    delay(1000, 'بيانات المستخدم'),
    delay(1500, 'بيانات الطلبات'),
    delay(800, 'بيانات التكوين')
  ]);

  output.textContent = 'تم تحميل الكل:\n' + results.join(' | ');
});

const raceBtn = document.createElement('button');
raceBtn.textContent = 'تحميل سباق';
document.body.appendChild(raceBtn);

raceBtn.addEventListener('click', async function() {
  const fastest = await Promise.race([
    delay(1000, 'المصدر A (1 ثانية)'),
    delay(500, 'المصدر B (0.5 ثانية)'),
    delay(800, 'المصدر C (0.8 ثانية)')
  ]);
  output.textContent = 'الأسرع: ' + fastest;
});
</script>
▶ جرّب الكود
💡 Promise.all مثل عشاء جماعي — يجب أن يصل الجميع قبل أن تأكل. Promise.race مثل سباق — من ينتهي أولاً يفوز. استخدم all للطلبات المستقلة لتسريع الأشياء، و race عندما يكون لديك عدة مصادر مرآة وتريد الأسرع.


📖 ملخص

  1. الكود المتزامن يعرض التنفيذ اللاحق؛ غير المتزامن لا يعرض — أحادي الخيط لا يزال يمكنه "فعل أشياء متعددة"
  2. الاستدعاءات هي النمط الأساسي؛ التداخل العميق يخلق جحيم الاستدعاءات
  3. Promise لديه ثلاث حالات: pending → fulfilled أو pending → rejected — انتقالات الحالة لا رجعة فيها
  4. then/catch/finally تستهلك Promises؛ التسلسل يحل جحيم الاستدعاءات
  5. async/await هو سكر صناعي فوق Promises؛ await يمكن استخدامه فقط داخل دوال async
  6. Promise.all ينتظر نجاح الجميع؛ Promise.race يأخذ أول من يحل

❓ أسئلة شائعة

س هل يمكن استخدام await في دالة عادية؟
ج لا. await يمكن استخدامه فقط داخل دالة async. استخدام await في دالة عادية يسبب خطأ صيغة. await المسطح مدعوم في الوحدات، لكن الدعم في المتصفحات يختلف.
س ماذا يحدث إذا فشل أحد Promises في Promise.all؟
ج Promise.all يرفض فوراً بالخطأ الأول. Promises الأخرى لا تزال تُنفذ، لكن نتائجها تُتجاهل. إذا كنت بحاجة إلى "الجميع يحل بغض النظر عن النتيجة"، استخدم Promise.allSettled.
س ماذا تُرجع دالة async؟
ج دالة async تُرجع دائماً Promise. حتى لو return 42، في الواقع تُرجع Promise.resolve(42). لذا يمكنك استخدام .then() لاستهلاك النتيجة.

📝 تمارين

  1. أساسي: استخدم Promise + setTimeout لمحاكاة تأخير ثانيتين، ثم اطبع "تم" عند النجاح.
  2. متوسط: أعد كتابة أعلاه بـ async/await وأضف معالجة أخطاء try/catch.
  3. تحدي: استخدم Promise.all لإطلاق 3 طلبات محاكاة بتأخيرات مختلفة (1 ثانية/2 ثانية/3 ثانية) متزامنة، ثم احسب واعرض الوقت الكلي المنقضي عندما تكتمل جميعها.
100%