دليل شامل لـ JavaScript Promises: الفهم العميق لـ Resolve, Reject, و Chaining

دقائق القراءة: 5

مقدمة إلى JavaScript Promises: التعامل مع العمليات غير المتزامنة

تُعد الـ Promises في JavaScript إحدى الطرق الأكثر فعالية وحداثة للتعامل مع العمليات غير المتزامنة (asynchronous operations). على الرغم من أهميتها، يواجه العديد من المطورين صعوبة في فهم آليتها وكيفية عملها بشكل صحيح. يهدف هذا المقال إلى تبسيط مفهوم الـ Promises وشرحها بطريقة واضحة ومباشرة، مع التركيز على الجوانب الأساسية مثل حالاتها، وكيفية استخدام resolve و reject، ومفهوم التسلسل (chaining) الذي يعزز من قابلية قراءة الكود.

سنتناول في هذا الدليل مقدمة شاملة لماهية الـ Promises، وشرحًا للمصطلحات الأساسية، بالإضافة إلى أمثلة برمجية عملية لإنشاء واستخدام الـ Promises. لفهم أعمق، يُنصح بمراجعة مفهوم Callbacks في JavaScript أولاً.

ما هي الـ Promise في JavaScript؟

يمكن تشبيه الـ Promise في JavaScript بالوعد الذي نقطعه في الحياة الواقعية. عندما نعد بشيء ما، فإننا نضمن القيام به في المستقبل، لأن الوعود دائمًا ما تتعلق بالزمن المستقبلي. للوعد في الحياة نتيجتان محتملتان: إما أن يتم الوفاء به أو لا يتم. الأمر ذاته ينطبق على الـ Promises في JavaScript؛ فعندما نُعرّف Promise، فإما أن يتم حلها (resolved) عند تحقق الشرط، أو يتم رفضها (rejected) عند فشل الشرط.

حالات الـ Promise في JavaScript

في جوهرها، الـ Promise هي كائن (object) يمر بثلاث حالات رئيسية تعكس دورة حياته:

  • حالة الانتظار (Pending): هذه هي الحالة الأولية للـ Promise، قبل أن يتم إنجازها بنجاح أو فشلها.
  • حالة النجاح (Resolved أو Fulfilled): تعني أن الـ Promise قد اكتملت بنجاح وتم الوفاء بها.
  • حالة الفشل (Rejected): تعني أن الـ Promise قد فشلت ولم يتم الوفاء بها.

مخطط يوضح دورة حياة حالات الـ Promise في JavaScript

على سبيل المثال، عندما نطلب بيانات من خادم باستخدام Promise، ستبقى في حالة pending حتى نستقبل البيانات. إذا نجحنا في الحصول على المعلومات من الخادم، فسيتم حل الـ Promise بنجاح (resolved). أما إذا لم نتمكن من الحصول على المعلومات، فستكون الـ Promise في حالة الرفض (rejected).

بالإضافة إلى ذلك، إذا كان هناك عدة طلبات متتالية، فبعد أن يتم حل (أو رفض) الـ Promise الأولى، ستبدأ عملية جديدة يمكننا ربطها مباشرةً باستخدام طريقة تسمى التسلسل (chaining).

الفرق بين Callbacks و Promises: لماذا نفضل Promises؟

يكمن الاختلاف الرئيسي بين دوال الاستدعاء (Callback Functions) والـ Promises في أننا نُرفق دالة الاستدعاء بالـ Promise بدلاً من تمريرها كمعامل. هذا يعني أننا ما زلنا نستخدم دوال الاستدعاء مع الـ Promises، ولكن بطريقة مختلفة (عبر التسلسل chaining). هذه إحدى أكبر مزايا استخدام الـ Promises، ولكن ما هو السبب؟

مفهوم التسلسل (Chaining) وكيف يحل مشكلة Callback Hell

لقد استُخدمت دوال الاستدعاء وحدها للعمليات غير المتزامنة في JavaScript لسنوات عديدة. ولكن في بعض الحالات، يمكن أن يكون استخدام الـ Promises خيارًا أفضل بكثير. إذا كان هناك العديد من العمليات غير المتزامنة (async operations) التي يجب إجراؤها، وحاولنا استخدام دوال الاستدعاء التقليدية لها، فسرعان ما سنجد أنفسنا في موقف يُعرف باسم Callback hell (جحيم دوال الاستدعاء):

firstRequest(function (response) {
  secondRequest(response, function (nextResponse) {
    thirdRequest(nextResponse, function (finalResponse) {
      console.log('Final response: ' + finalResponse);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

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

firstRequest()
  .then(function (response) {
    return secondRequest(response);
  })
  .then(function (nextResponse) {
    return thirdRequest(nextResponse);
  })
  .then(function (finalResponse) {
    console.log('Final response: ' + finalResponse);
  })
  .catch(failureCallback);

يوضح الكود أعلاه كيف يمكن ربط عدة دوال استدعاء الواحدة تلو الأخرى بطريقة متسلسلة. يُعد التسلسل (chaining) أحد أفضل ميزات الـ Promises، حيث يحسن بشكل كبير من قابلية قراءة الكود وهيكلته، ويجعل التعامل مع الأخطاء أكثر مركزية وسهولة.

إنشاء واستخدام الـ Promise خطوة بخطوة

1. إنشاء كائن Promise

أولاً، نستخدم مُنشئ (constructor) لإنشاء كائن Promise:

const myPromise = new Promise();

2. تحديد دوال رد النداء (resolve, reject)

يأخذ المُنشئ معاملين، أحدهما للنجاح (resolve) والآخر للفشل (reject):

const myPromise = new Promise((resolve, reject) => {
  // condition
});

3. إضافة الشرط ومعالجة النتيجة

أخيرًا، سيكون هناك شرط. إذا تم استيفاء الشرط، فسيتم حل الـ Promise (resolved)، وإلا فسيتم رفضها (rejected):

const myPromise = new Promise((resolve, reject) => {
  let condition = true; // مثال: يمكن أن يكون نتيجة عملية غير متزامنة
  if (condition) {
    resolve('Promise is resolved successfully.');
  } else {
    reject('Promise is rejected');
  }
});

لقد أنشأنا الآن أول Promise لنا. دعنا نرى كيفية استخدامها.

استخدام .then() للـ Promises الناجحة (Resolved)

إذا رجعت إلى الصورة في بداية هذا المقال، سترى أن هناك حالتين: واحدة للـ Promises التي تم حلها، وواحدة للتي تم رفضها. إذا تم حل الـ Promise (حالة النجاح)، فسيحدث شيء ما بعد ذلك (يعتمد على ما نفعله بالـ Promise الناجحة).

myPromise.then();

يتم استدعاء الدالة .then() بعد حل الـ Promise. بعد ذلك، يمكننا تحديد ما يجب فعله بالـ Promise التي تم حلها. على سبيل المثال، دعنا نسجل الرسالة التي حصلنا عليها من الـ Promise في وحدة التحكم (console):

myPromise.then((message) => {
  console.log(message);
});

استخدام .catch() للـ Promises الفاشلة (Rejected)

ومع ذلك، فإن الدالة .then() مخصصة فقط للـ Promises التي تم حلها. ماذا لو فشلت الـ Promise؟ في هذه الحالة، نحتاج إلى استخدام الدالة .catch(). تمامًا كما نُرفق الدالة .then()، يمكننا أيضًا إرفاق الدالة .catch() مباشرة بعد .then():

myPromise
  .then((message) => {
    console.log(message);
  })
  .catch((message) => {
    console.log(message);
  });

لذا، إذا تم رفض الـ Promise، فستنتقل إلى الدالة .catch()، وهذه المرة سنرى رسالة مختلفة في وحدة التحكم (console).

الخلاصة التقنية

تُعد الـ Promises أداة قوية وضرورية في عالم JavaScript الحديث، خاصة عند التعامل مع تعقيدات العمليات غير المتزامنة. لقد أحدثت ثورة في كيفية كتابة الكود القابل للقراءة والصيانة، متجاوزةً التحديات التي كانت تفرضها Callback hell. من خلال فهم حالات الـ Promise (pending، resolved، rejected) وكيفية استخدام دوال resolve و reject، بالإضافة إلى قوة التسلسل (chaining) عبر .then() و .catch()، يمكن للمطورين بناء تطبيقات أكثر استقرارًا ومرونة. إن إتقان الـ Promises ليس مجرد مهارة إضافية، بل هو حجر الزاوية في تطوير الويب الحديث الذي يعتمد بشكل كبير على التفاعل غير المتزامن.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *