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

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


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

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

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

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

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



▶ مثال

JAVASCRIPT
// مثال على الكود في هذا القسم
const message = "مرحبا بالعالم!";
console.log(message);
▶ جرّب الكود

هذا أبسط مثال; الأقسام التالية ستشرح بالتفصيل.## 2. الاستدعاءات (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>
▶ جرّب الكود

(1) جحيم الاستدعاءات (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 لحل هذا.



2. Promise

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

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

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

(1) إنشاء Promise

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

(2) 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>
▶ جرّب الكود

(3) التسلسل

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.



3. 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 يحول "خذ رقماً وانتظر" إلى "ابقَ في الصف دون مغادرة" — تدفق الكود مباشقر والقراءة ترتفع بشكل كبير.

(1) معالجة الأخطاء

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>


4. 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 عندما يكون لديك عدة مصادر مرآة وتريد الأسرع.



❓ أسئلة شائعة

س هل يمكن استخدام 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 ثانية) متزامنة، ثم احسب واعرض الوقت الكلي المنقضي عندما تكتمل جميعها.
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%