دليل شامل لـ JavaScript Promises: فهم حالاتها وكيفية التعامل معها

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

تُعدّ الـ Promises (الوعود) لبنات بناء أساسية للتعامل مع العمليات غير المتزامنة في لغة جافاسكريبت (JavaScript). قد يظن البعض أن فهم الوعود والعمل بها ليس بالأمر السهل، وصدقني، لست وحدك من يشعر بذلك! فكثير من مطوري الويب يجدون الوعود تحديًا، حتى بعد سنوات من العمل معها.

في هذا المقال، سأسعى جاهدًا لتغيير هذا التصور، مشاركًا ما تعلمته عن JavaScript Promises على مدار السنوات الماضية. آمل أن تجد هذا الدليل مفيدًا للغاية في رحلتك لتعزيز مهاراتك في JavaScript.

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

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

يمكنك إنشاء وعد (Promise) باستخدام مُنشئ الوعود (Promise constructor) على النحو التالي:

 let promise = new Promise ( function ( resolve, reject ) {
 // قم بإجراء مكالمة غير متزامنة ثم قم إما بالحل (resolve) أو الرفض (reject)
 });

في معظم الحالات، يُستخدم الـ Promise للعمليات غير المتزامنة. ومع ذلك، من الناحية الفنية، يمكنك استخدام resolve/reject لكل من العمليات المتزامنة وغير المتزامنة.

الـ Callbacks مقابل الـ Promises: معالجة “جحيم الكولباك”

قد تتساءل: أليس لدينا دوال callback للعمليات غير المتزامنة؟ نعم، هذا صحيح! لدينا دوال callback في JavaScript. لكن الـ callback ليست شيئًا خاصًا في JavaScript؛ إنها دالة عادية تُنتج نتائج بعد اكتمال استدعاء غير متزامن (سواء بنجاح أو فشل). كلمة “غير متزامن” تعني أن شيئًا ما سيحدث في المستقبل، وليس الآن. عادةً، تُستخدم الـ callbacks فقط عند القيام بأشياء مثل استدعاءات الشبكة، أو تحميل/تنزيل الملفات، أو التفاعل مع قواعد البيانات، وما إلى ذلك.

بينما تُعدّ الـ callbacks مفيدة، إلا أن لها جانبًا سلبيًا كبيرًا. في بعض الأحيان، قد نجد أنفسنا نستخدم callback داخل callback أخرى، والتي بدورها داخل callback أخرى، وهكذا دواليك. هذا ما يُعرف بـ “جحيم الكولباك” (Callback Hell)، وهو نمط برمجي يؤدي إلى كود يصعب قراءته وصيانته. دعنا نفهم هذا “الجحيم” بمثال عملي.

مثال PizzaHub: كيف نتجنب “جحيم الكولباك”؟

لنفترض أننا نطلب بيتزا “فيج مارجريتا” 🍕 من تطبيق “PizzaHub”. عند تقديم الطلب، يقوم “PizzaHub” تلقائيًا باكتشاف موقعنا، ويجد أقرب مطعم بيتزا، ويتأكد من توفر البيتزا المطلوبة. إذا كانت متوفرة، فإنه يحدد نوع المشروبات المجانية التي نحصل عليها مع البيتزا، وأخيرًا، يقوم بتقديم الطلب. إذا تم تقديم الطلب بنجاح، نتلقى رسالة تأكيد.

كيف يمكننا برمجة هذا باستخدام دوال callback؟ إليك مثال على ذلك:

 function orderPizza ( type, name ) {
 // الاستعلام عن متجر البيتزا من PizzaHub
 query( `/api/pizzahub/` , function ( result, error ) {
 if (!error) {
 let shopId = result.shopId;
 // الحصول على المتجر والاستعلام عن البيتزا
 query( `/api/pizzahub/pizza/ ${shopId} ` , function ( result, error ) {
 if (!error) {
 let pizzas = result.pizzas;
 // البحث عما إذا كانت البيتزا الخاصة بي متاحة
 let myPizza = pizzas.find( ( pizza ) => {
 return (pizza.type===type && pizza.name===name);
 });
 // التحقق من المشروبات المجانية
 query( `/api/pizzahub/beverages/ ${myPizza.id} ` , function ( result, error ) {
 if (!error) {
 let beverage = result.id;
 // إعداد الطلب
 query( `/api/order` , {
 'type' : type,
 'name' : name,
 'beverage' : beverage},
 function ( result, error ) {
 if (!error) {
 console .log( `Your order of ${type} ${name} with ${beverage} has been placed` );
 } else {
 console .log( `Bad luck, No Pizza for you today!` );
 }
 });
 }
 })
 }
 });
 }
 });
 }
 // استدعاء دالة orderPizza
 orderPizza( 'veg' , 'margherita' );

دعنا نلقي نظرة فاحصة على دالة orderPizza() في الكود أعلاه. إنها تستدعي واجهة برمجة تطبيقات (API) للحصول على معرف متجر البيتزا القريب منك. بعد ذلك، تحصل على قائمة البيتزا المتاحة في ذلك المطعم. تتحقق مما إذا كانت البيتزا التي نطلبها موجودة، وتُجري استدعاء API آخر للعثور على المشروبات المتاحة مع تلك البيتزا. أخيرًا، يقوم API الخاص بالطلب بتقديم الطلب.

هنا، نستخدم callback لكل استدعاء من استدعاءات API. هذا يقودنا إلى استخدام callback أخرى داخل السابقة، وهكذا دواليك. هذا يعني أننا ندخل في ما نسميه (بتعبير شديد) “جحيم الكولباك” (Callback Hell). ومن منا يرغب في ذلك؟ إنه يشكل أيضًا هرمًا من الكود ليس فقط مربكًا ولكن أيضًا عرضة للأخطاء.

رسم توضيحي لجحيم الكولباك وهيكل الكود الهرمي

هناك عدة طرق للخروج من (أو عدم الوقوع في) “جحيم الكولباك”. الطريقة الأكثر شيوعًا هي استخدام الـ Promise أو دالة async. ومع ذلك، لفهم دوال async جيدًا، تحتاج أولاً إلى فهم جيد للـ Promises. لذا، دعنا نبدأ ونغوص في عالم الوعود.

فهم حالات الـ Promise (الوعود)

للتذكير، يمكن إنشاء الـ Promise باستخدام بناء المُنشئ (constructor syntax) على النحو التالي:

 let promise = new Promise ( function ( resolve, reject ) {
 // الكود المراد تنفيذه
 });

تأخذ دالة المُنشئ دالة كوسيط. تُسمى هذه الدالة بـ “دالة المُنفِّذ” (executor function).

 // دالة المُنفِّذ التي تُمرر إلى
 // مُنشئ الـ Promise كوسيط
 function ( resolve, reject ) {
 // منطقك البرمجي يوضع هنا...
 }

تأخذ دالة المُنفِّذ وسيطين: resolve و reject. هذه هي دوال الـ callback التي توفرها لغة JavaScript. يوضع منطقك البرمجي داخل دالة المُنفِّذ التي تُشغّل تلقائيًا عند إنشاء new Promise(). لكي يكون الـ Promise فعالًا، يجب أن تستدعي دالة المُنفِّذ إحدى دالتي الـ callback، إما resolve أو reject. سنتعلم المزيد عن هذا بالتفصيل لاحقًا.

يُرجع المُنشئ new Promise() كائن promise. وبما أن دالة المُنفِّذ تحتاج إلى التعامل مع العمليات غير المتزامنة، يجب أن يكون كائن الـ promise المُرجع قادرًا على الإبلاغ عن متى بدأ التنفيذ، ومتى اكتمل (resolved)، أو متى عاد بخطأ (rejected).

يحتوي كائن الـ promise على الخصائص الداخلية التالية:

  • state – يمكن أن تأخذ هذه الخاصية القيم التالية:
    • pending: مبدئيًا عندما تبدأ دالة المُنفِّذ التنفيذ.
    • fulfilled: عندما يتم حل الـ promise بنجاح.
    • rejected: عندما يتم رفض الـ promise بسبب خطأ.

رسم بياني يوضح حالات الـ Promise المختلفة

  • result – يمكن أن تأخذ هذه الخاصية القيم التالية:
    • undefined: مبدئيًا عندما تكون قيمة state هي pending.
    • value: عندما تُستدعى resolve(value).
    • error: عندما تُستدعى reject(error).

هذه الخصائص الداخلية لا يمكن الوصول إليها برمجياً بشكل مباشر، ولكن يمكن فحصها. هذا يعني أننا سنتمكن من فحص قيم خصائص state و result باستخدام أداة المُصحح (debugger tool)، لكننا لن نتمكن من الوصول إليها مباشرة باستخدام البرنامج.

لقطة شاشة توضح فحص الخصائص الداخلية للـ Promise باستخدام أدوات المطور

يمكن أن تكون حالة الـ promise إما pending (معلقة)، أو fulfilled (مكتملة)، أو rejected (مرفوضة). يُطلق على الـ promise الذي تم حله أو رفضه اسم settled (مستقر).

رسم بياني يوضح أن الـ Promise المستقر إما أن يكون مكتملًا أو مرفوضًا

كيف يتم حل ورفض الـ Promises

إليك مثال على promise سيتم حله (حالة fulfilled) بالقيمة "I am done" على الفور:

 let promise = new Promise ( function ( resolve, reject ) {
 resolve( "I am done" );
 });

الـ promise أدناه سيتم رفضه (حالة rejected) برسالة الخطأ "Something is not right!":

 let promise = new Promise ( function ( resolve, reject ) {
 reject( new Error ( 'Something is not right!' ));
 });

نقطة مهمة يجب ملاحظتها: يجب أن تستدعي دالة مُنفِّذ الـ Promise دالة resolve واحدة فقط أو دالة reject واحدة فقط. بمجرد تغيير حالة واحدة (من pending إلى fulfilled أو من pending إلى rejected)، هذا كل شيء. سيتم تجاهل أي استدعاءات أخرى لـ resolve أو reject.

 let promise = new Promise ( function ( resolve, reject ) {
 resolve( "I am surely going to get resolved!" );
 reject( new Error ( 'Will this be ignored?' )); // سيتم تجاهلها
 resolve( "Ignored?" ); // سيتم تجاهلها
 });

في المثال أعلاه، سيتم استدعاء الدالة الأولى التي تقوم بالحل (resolve) فقط، وسيتم تجاهل البقية.

كيفية التعامل مع الـ Promise بعد إنشائه

يستخدم الـ Promise دالة مُنفِّذ (executor function) لإكمال مهمة (غالبًا ما تكون غير متزامنة). يجب أن يتم إخطار دالة المستهلك (consumer function) التي تستخدم نتيجة الـ promise عندما تنتهي دالة المُنفِّذ إما بالحل (نجاح) أو الرفض (خطأ).

تساعد دوال المعالجة (handler methods) مثل .then() و .catch() و .finally() في إنشاء الرابط بين دالتي المُنفِّذ والمستهلك بحيث يمكنهما أن تكونا متزامنتين عندما يتم حل الـ promise أو رفضه.

رسم توضيحي للعلاقة بين دالتي المُنفِّذ والمستهلك في الـ Promise

استخدام معالج الـ Promise: .then()

يجب استدعاء دالة .then() على كائن الـ promise للتعامل مع النتيجة (resolve) أو الخطأ (reject). تقبل هذه الدالة دالتين كمعاملات. عادةً، يجب استدعاء دالة .then() من دالة المستهلك حيث ترغب في معرفة نتيجة تنفيذ الـ promise.

promise.then(
 ( result ) => {
 console .log(result);
 },
 ( error ) => {
 console .log(error);
 }
 );

إذا كنت مهتمًا فقط بالنتائج الناجحة، يمكنك تمرير وسيط واحد فقط إليها، مثل هذا:

promise.then( ( result ) => {
 console .log(result);
 } );

إذا كنت مهتمًا فقط بنتيجة الخطأ، يمكنك تمرير null للوسيط الأول، مثل هذا:

promise.then( null , ( error ) => {
 console .log(error)
 } );

ومع ذلك، يمكنك التعامل مع الأخطاء بطريقة أفضل باستخدام دالة .catch() التي سنراها بعد قليل.

دعنا نلقي نظرة على بعض الأمثلة للتعامل مع النتائج والأخطاء باستخدام معالجات .then() و .catch(). سنجعل هذا التعلم أكثر متعة من خلال بعض الطلبات غير المتزامنة الحقيقية. سنستخدم PokeAPI للحصول على معلومات حول البوكيمونات وحلها/رفضها باستخدام الـ Promises.

أولاً، دعنا ننشئ دالة عامة تقبل عنوان URL لـ PokeAPI كوسيط وتُرجع Promise. إذا كان استدعاء API ناجحًا، يتم إرجاع promise محلول. يتم إرجاع promise مرفوض لأي نوع من الأخطاء. سنستخدم هذه الدالة في عدة أمثلة من الآن فصاعدًا للحصول على promise والعمل عليه.

 function getPromise ( URL ) {
 let promise = new Promise ( function ( resolve, reject ) {
 let req = new XMLHttpRequest();
 req.open( "GET" , URL);
 req.onload = function ( ) {
 if (req.status == 200 ) {
 resolve(req.response);
 } else {
 reject( "There is an Error!" );
 }
 };
 req.send();
 });
 return promise;
 }

مثال 1: الحصول على معلومات 50 بوكيمون

 const ALL_POKEMONS_URL = 'https://pokeapi.co/api/v2/pokemon?limit=50' ;
 // لقد ناقشنا هذه الدالة بالفعل!
 let promise = getPromise(ALL_POKEMONS_URL);
 const consumer = () => {
 promise.then(
 ( result ) => {
 console .log({result}); // تسجيل نتيجة 50 بوكيمون
 },
 ( error ) => {
 // بما أن الـ URL صالح، لن يتم استدعاء هذا.
 console .log( 'We have encountered an Error!' ); // تسجيل خطأ
 }
 );
 }
 consumer();

مثال 2: تجربة عنوان URL غير صالح

 const POKEMONS_BAD_URL = 'https://pokeapi.co/api/v2/pokemon-bad/' ;
 // هذا سيرفض لأن الـ URL يعطي خطأ 404
 let promise = getPromise(POKEMONS_BAD_URL);
 const consumer = () => {
 promise.then(
 ( result ) => {
 // الـ promise لم يتم حله. وبالتالي، لن يتم
 // تنفيذه.
 console .log({result});
 },
 ( error ) => {
 // الـ promise المرفوض سينفذ هذا
 console .log( 'We have encountered an Error!' ); // تسجيل خطأ
 }
 );
 }
 consumer();

استخدام معالج الـ Promise: .catch()

يمكنك استخدام دالة المعالجة هذه للتعامل مع الأخطاء (عمليات الرفض) من الـ promises. إن صيغة تمرير null كوسيط أول لدالة .then() ليست طريقة رائعة للتعامل مع الأخطاء. لذلك، لدينا دالة .catch() للقيام بنفس المهمة بصيغة أنيقة:

 // هذا سيرفض لأن الـ URL يعطي خطأ 404
 let promise = getPromise(POKEMONS_BAD_URL);
 const consumer = () => {
 promise.catch( error => console .log(error));
 }
 consumer();

إذا قمنا بإلقاء خطأ (throw new Error("Something wrong!")) بدلاً من استدعاء reject من دالة مُنفِّذ الـ promise أو المعالجات، فسيظل يُعامل على أنه رفض. هذا يعني أنه سيتم التقاطه بواسطة دالة المعالجة .catch(). وينطبق الشيء نفسه على أي استثناءات متزامنة تحدث في دالة مُنفِّذ الـ promise ودوال المعالجة.

إليك مثال حيث سيتم التعامل معه كرفض وسيتم استدعاء دالة المعالجة .catch():

 new Promise ( ( resolve, reject ) => {
 throw new Error ( "Something is wrong!" ); // لا يوجد استدعاء لـ reject
 }).catch( ( error ) => console .log(error));

استخدام معالج الـ Promise: .finally()

تقوم دالة المعالجة .finally() بإجراء عمليات تنظيف مثل إيقاف مؤشر التحميل (loader)، أو إغلاق اتصال حي، وما إلى ذلك. سيتم استدعاء دالة finally() بغض النظر عما إذا كان الـ promise قد تم حله (resolve) أو رفضه (reject). إنها تمرر النتيجة أو الخطأ إلى المعالج التالي الذي يمكنه استدعاء .then() أو .catch() مرة أخرى.

إليك مثال سيساعدك على فهم جميع الدوال الثلاث معًا:

 let loading = true ;
 loading && console .log( 'Loading...' );
 // الحصول على Promise
 promise = getPromise(ALL_POKEMONS_URL);
 promise.finally( () => {
 loading = false ;
 console .log( `Promise Settled and loading is ${loading} ` );
 }).then( ( result ) => {
 console .log({result});
 }).catch( ( error ) => {
 console .log(error)
 });

للتوضيح أكثر:

  • تقوم دالة .finally() بجعل loading بقيمة false.
  • إذا تم حل الـ promise، فسيتم استدعاء دالة .then().
  • إذا تم رفض الـ promise بخطأ، فسيتم استدعاء دالة .catch().
  • سيتم استدعاء .finally() بغض النظر عن الحل أو الرفض.

ما هي سلسلة الـ Promises (Promise Chain)؟

دائمًا ما يُرجع استدعاء promise.then() وعدًا جديدًا (promise). سيكون لهذا الـ promise الجديد حالة pending وقيمة result هي undefined. هذا يسمح لنا باستدعاء دالة .then() التالية على الـ promise الجديد. عندما تُرجع دالة .then() الأولى قيمة، يمكن لدالة .then() التالية أن تتلقى تلك القيمة. يمكن للثانية الآن تمريرها إلى دالة .then() الثالثة وهكذا. هذا يشكل سلسلة من دوال .then() لتمرير الـ promises إلى الأسفل. تُسمى هذه الظاهرة “سلسلة الـ Promises” (Promise Chain).

رسم بياني يوضح مفهوم سلسلة الـ Promises

إليك مثال:

 let promise = getPromise(ALL_POKEMONS_URL);
 promise.then( result => {
 let onePokemon = JSON .parse(result).results[ 0 ].url;
 return onePokemon;
 }).then( onePokemonURL => {
 console .log(onePokemonURL);
 }).catch( error => {
 console .log( 'In the catch' , error);
 });

هنا، نحصل أولاً على promise محلول ثم نستخرج عنوان URL للوصول إلى أول بوكيمون. ثم نعيد تلك القيمة وسيتم تمريرها كوعد إلى دالة المعالجة .then() التالية. وبالتالي، يكون الإخراج:

https://pokeapi.co/api/v2/pokemon/1/

يمكن لدالة .then() أن تُرجع إما:

  • قيمة (لقد رأينا هذا بالفعل).
  • promise جديد تمامًا.
  • يمكنها أيضًا إلقاء خطأ (throw an error).

إليك مثال حيث أنشأنا سلسلة promise باستخدام دوال .then() التي تُرجع النتائج و promise جديدًا:

 // سلسلة Promise مع دالات then و catch متعددة
 let promise = getPromise(ALL_POKEMONS_URL);
 promise.then( result => {
 let onePokemon = JSON .parse(result).results[ 0 ].url;
 return onePokemon;
 }).then( onePokemonURL => {
 console .log(onePokemonURL);
 return getPromise(onePokemonURL);
 }).then( pokemon => {
 console .log( JSON .parse(pokemon));
 }).catch( error => {
 console .log( 'In the catch' , error);
 });

في أول استدعاء .then()، نستخرج عنوان URL ونعيده كقيمة. سيتم تمرير هذا الـ URL إلى استدعاء .then() الثاني حيث نُرجع promise جديدًا يأخذ هذا الـ URL كوسيط. سيتم حل هذا الـ promise وتمريره إلى السلسلة حيث نحصل على معلومات حول البوكيمون.

إليك الإخراج:

لقطة شاشة توضح إخراج استدعاء سلسلة الـ Promise

في حالة وجود خطأ أو رفض promise، سيتم استدعاء دالة .catch() في السلسلة.

نقطة يجب ملاحظتها: استدعاء .then() عدة مرات على نفس الـ promise لا يشكل سلسلة Promise. قد ينتهي بك الأمر إلى القيام بشيء كهذا فقط لإدخال خطأ في الكود:

 let promise = getPromise(ALL_POKEMONS_URL);
 promise.then( result => {
 let onePokemon = JSON .parse(result).results[ 0 ].url;
 return onePokemon;
 });
 promise.then( onePokemonURL => {
 console .log(onePokemonURL);
 return getPromise(onePokemonURL);
 });
 promise.then( pokemon => {
 console .log( JSON .parse(pokemon));
 });

نحن نستدعي دالة .then() ثلاث مرات على نفس الـ promise، لكننا لا نمرر الـ promise إلى الأسفل. هذا يختلف عن سلسلة الـ Promise. في المثال أعلاه، سيكون الإخراج خطأ.

لقطة شاشة توضح خطأ عند عدم تشكيل سلسلة Promise بشكل صحيح

كيفية التعامل مع الـ Promises المتعددة

بالإضافة إلى دوال المعالجة (.then()، .catch()، و .finally())، هناك ست دوال ثابتة (static methods) متاحة في واجهة برمجة تطبيقات الـ Promise. تقبل الدوال الأربع الأولى مصفوفة من الـ promises وتُشغّلها بالتوازي:

  • Promise.all()
  • Promise.any()
  • Promise.allSettled()
  • Promise.race()
  • Promise.resolve()
  • Promise.reject()

دعنا نمر على كل واحدة منها.

دالة Promise.all()

تقبل دالة Promise.all([promises]) مجموعة (مثل مصفوفة) من الـ promises كوسيط وتنفذها بالتوازي. تنتظر هذه الدالة حتى يتم حل جميع الـ promises وتُرجع مصفوفة من نتائج الـ promises. إذا تم رفض أي من الـ promises أو فشل في التنفيذ بسبب خطأ، فسيتم تجاهل جميع نتائج الـ promises الأخرى.

دعنا ننشئ ثلاثة promises للحصول على معلومات حول ثلاثة بوكيمونات:

 const BULBASAUR_POKEMONS_URL = 'https://pokeapi.co/api/v2/pokemon/bulbasaur' ;
 const RATICATE_POKEMONS_URL = 'https://pokeapi.co/api/v2/pokemon/raticate' ;
 const KAKUNA_POKEMONS_URL = 'https://pokeapi.co/api/v2/pokemon/kakuna' ;
 let promise_1 = getPromise(BULBASAUR_POKEMONS_URL);
 let promise_2 = getPromise(RATICATE_POKEMONS_URL);
 let promise_3 = getPromise(KAKUNA_POKEMONS_URL);

استخدم دالة Promise.all() عن طريق تمرير مصفوفة من الـ promises:

 Promise .all([promise_1, promise_2, promise_3]).then( result => {
 console .log({result});
 }).catch( error => {
 console .log( 'An Error Occured' );
 });

الإخراج:

لقطة شاشة توضح إخراج Promise.all مع نتائج البوكيمونات الثلاثة

كما ترى في الإخراج، يتم إرجاع نتائج جميع الـ promises. الوقت اللازم لتنفيذ جميع الـ promises يساوي أقصى وقت يستغرقه أي promise للتشغيل.

دالة Promise.any()

دالة Promise.any([promises]) – على غرار دالة all()، تقبل .any() أيضًا مصفوفة من الـ promises لتنفيذها بالتوازي. لا تنتظر هذه الدالة حتى يتم حل جميع الـ promises. بل تكتمل عندما يتم تسوية (settled) أي promise واحد فقط.

 Promise .any([promise_1, promise_2, promise_3]).then( result => {
 console .log( JSON .parse(result));
 }).catch( error => {
 console .log( 'An Error Occured' );
 });

سيكون الإخراج هو نتيجة أي من الـ promises التي تم حلها أولاً:

لقطة شاشة توضح إخراج Promise.any مع نتيجة أول بوكيمون يتم حله

دالة Promise.allSettled()

دالة Promise.allSettled([promises]) – تنتظر هذه الدالة حتى يتم تسوية جميع الـ promises (سواء تم حلها أو رفضها) وتُرجع نتائجها كمصفوفة من الكائنات. ستحتوي النتائج على حالة (fulfilled/rejected) وقيمة، إذا تم حلها. في حالة الرفض، ستُرجع سبب الخطأ.

إليك مثال على جميع الـ promises التي تم حلها:

 Promise .allSettled([promise_1, promise_2, promise_3]).then( result => {
 console .log({result});
 }).catch( error => {
 console .log( 'There is an Error!' );
 });

الإخراج:

لقطة شاشة توضح إخراج Promise.allSettled مع حالات ونتائج جميع الـ Promises

إذا تم رفض أي من الـ promises، على سبيل المثال، promise_1:

 let promise_1 = getPromise(POKEMONS_BAD_URL);

لقطة شاشة توضح إخراج Promise.allSettled مع رفض أحد الـ Promises

دالة Promise.race()

دالة Promise.race([promises]) – تنتظر هذه الدالة أول promise يتم تسويته (الأسرع)، وتُرجع النتيجة/الخطأ وفقًا لذلك.

 Promise .race([promise_1, promise_2, promise_3]).then( result => {
 console .log( JSON .parse(result));
 }).catch( error => {
 console .log( 'An Error Occured' );
 });

إخراج أسرع promise تم حله:

لقطة شاشة توضح إخراج Promise.race مع نتيجة أسرع Promise تم حله

دالتا Promise.resolve() و Promise.reject()

دالة Promise.resolve(value) – تقوم بحل promise بالقيمة التي تم تمريرها إليها. وهي مكافئة لما يلي:

 let promise = new Promise ( resolve => resolve(value));

دالة Promise.reject(error) – تقوم برفض promise بالخطأ الذي تم تمريره إليها. وهي مكافئة لما يلي:

 let promise = new Promise ( ( resolve, reject ) => reject(error));

هل يمكننا إعادة كتابة مثال PizzaHub باستخدام الـ Promises؟

بالتأكيد، دعنا نفعل ذلك. دعنا نفترض أن دالة query() ستُرجع promise. إليك مثال على دالة query(). في الواقع، قد تتفاعل هذه الدالة مع قاعدة بيانات وتُرجع النتائج. في هذه الحالة، هي مبرمجة بشكل ثابت ولكنها تخدم نفس الغرض.

 function query ( endpoint ) {
 if (endpoint === `/api/pizzahub/` ) {
 return new Promise ( ( resolve, reject ) => {
 resolve({ 'shopId' : '123' });
 })
 } else if (endpoint.indexOf( '/api/pizzahub/pizza/' ) >= 0 ) {
 return new Promise ( ( resolve, reject ) => {
 resolve({ pizzas : [{ 'type' : 'veg' , 'name' : 'margherita' , 'id' : '123' }]});
 })
 } else if (endpoint.indexOf( '/api/pizzahub/beverages' ) >= 0 ) {
 return new Promise ( ( resolve, reject ) => {
 resolve({ id : '10' , 'type' : 'veg' , 'name' : 'margherita' , 'beverage' : 'coke' });
 })
 } else if (endpoint === `/api/order` ) {
 return new Promise ( ( resolve, reject ) => {
 resolve({ 'type' : 'veg' , 'name' : 'margherita' , 'beverage' : 'coke' });
 })
 }
 }

الخطوة التالية هي إعادة هيكلة “جحيم الكولباك” الخاص بنا. للقيام بذلك، سنقوم أولاً بإنشاء بعض الدوال المنطقية:

 // تُرجع معرّف المتجر
 let getShopId = result => result.shopId;
 // تُرجع promise بقائمة البيتزا لمتجر معين
 let getPizzaList = shopId => {
 const url = `/api/pizzahub/pizza/ ${shopId} ` ;
 return query(url);
 }
 // تُرجع promise بالبيتزا التي تطابق طلب العميل
 let getMyPizza = ( result, type, name ) => {
 let pizzas = result.pizzas;
 let myPizza = pizzas.find( ( pizza ) => {
 return (pizza.type===type && pizza.name===name);
 });
 const url = `/api/pizzahub/beverages/ ${myPizza.id} ` ;
 return query(url);
 }
 // تُرجع promise بعد تقديم الطلب
 let performOrder = result => {
 let beverage = result.id;
 return query( `/api/order` , {
 'type' : result.type,
 'name' : result.name,
 'beverage' : result.beverage});
 }
 // تأكيد الطلب
 let confirmOrder = result => {
 console .log( `Your order of ${result.type} ${result.name} with ${result.beverage} has been placed!` );
 }

استخدم هذه الدوال لإنشاء الـ promises المطلوبة. هنا يجب أن تقارن هذا المثال بمثال “جحيم الكولباك”. هذا أنيق وواضح للغاية:

 function orderPizza ( type, name ) {
 query( `/api/pizzahub/` )
 .then( result => getShopId(result))
 .then( shopId => getPizzaList(shopId))
 .then( result => getMyPizza(result, type, name))
 .then( result => performOrder(result))
 .then( result => confirmOrder(result))
 .catch( function ( error ) {
 console .log( `Bad luck, No Pizza for you today!` );
 })
 }

أخيرًا، استدعِ دالة orderPizza() بتمرير نوع البيتزا واسمها، مثل هذا:

orderPizza( 'veg' , 'margherita' );

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

إذا وصلت إلى هنا وقرأت معظم الأسطر أعلاه، فتهانينا! يجب أن يكون لديك الآن فهم أفضل لـ JavaScript Promises. جميع الأمثلة المستخدمة في هذا المقال متوفرة في هذا المستودع على GitHub.

الخطوة التالية هي التعرف على دالة async في JavaScript التي تبسط الأمور بشكل أكبر. أفضل طريقة لتعلم مفهوم JavaScript promises هي من خلال كتابة أمثلة صغيرة والبناء عليها. بغض النظر عن الإطار أو المكتبة (Angular، React، Vue، وما إلى ذلك) التي نستخدمها، فإن العمليات غير المتزامنة لا مفر منها. هذا يعني أنه يتعين علينا فهم الـ promises لجعل الأمور تعمل بشكل أفضل.

أيضًا، أنا متأكد من أنك ستجد استخدام دالة fetch أسهل بكثير الآن:

fetch( '/api/user.json' )
 .then( function ( response ) {
 return response.json();
 })
 .then( function ( json ) {
 console .log(json); // {"name": "tapas", "blog": "freeCodeCamp"}
 });

تُرجع دالة fetch وعدًا (promise). لذا يمكننا استدعاء دالة المعالجة .then() عليها. والباقي يتعلق بسلسلة الـ promise التي تعلمناها في هذا المقال.

في الختام، تُعدّ الـ Promises أداة لا غنى عنها في عالم تطوير الويب الحديث، حيث توفر حلاً أنيقًا وفعالًا للتعامل مع التحديات التي تطرحها العمليات غير المتزامنة. من خلال فهم حالاتها، وكيفية استخدام معالجاتها، وبناء سلاسل Promises، يمكن للمطورين كتابة كود أكثر نظافة، قابلية للقراءة، وأقل عرضة للأخطاء. إن إتقان الـ Promises يمهد الطريق لفهم أعمق لـ async/await، مما يعزز قدرتك على بناء تطبيقات ويب قوية وسريعة الاستجابة.

شكرًا لك على القراءة حتى هذه النقطة! دعنا نتواصل. يمكنك التواصل معي عبر تويتر (@tapasadhikary) لترك تعليقاتك. قد تعجبك أيضًا هذه المقالات الأخرى:

هذا كل شيء في الوقت الحالي. أراك قريبًا في مقالي التالي. حتى ذلك الحين، يرجى الاعتناء بنفسك جيدًا.

Tapas Adhikary
Co-Founder, @CreoWis | Teacher, @tapaScript | Founder, @ReactPlay | YouTuber | Writer | Human

اترك تعليقاً

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