ثمّة 5 توابِع ثابتة (static) في صنف الوعود Promise
. سنشرح الآن عن استعمالاتها سريعًا.
Promise.all
لنقل بأنّك تريد تنفيذ أكثر من وعد واحد في وقت واحد، والانتظار حتّى تجهز جميعها.
مثلًا أن تُنزّل أكثر من عنوان URL في آن واحد وتُعالج المحتوى ما إن تُنزّل كلها.
وهذا الغرض من وجود Promise.all
. إليك صياغته:
let promise = Promise.all([...promises...]);
يأخذ التابِع Promise.all
مصفوفة من الوعود (تقنيًا يمكن أن تكون أيّ … ولكنّها في العادة مصفوفة) ويُعيد وعدًا جديدًا.
لا يُحلّ الوعد الجديد إلّا حين تستقرّ الوعود في المصفوفة، وتصير تلك المصفوفة التي تحمل نواتج الوعود - تصير ناتج الوعد الجديد.
مثال على ذلك تابِع Promise.all
أسفله إذ يستقرّ بعد 3 ثوان ويستلم نواتجه في مصفوفة [1, 2, 3]
:
Promise.all([ new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1 new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2 new Promise(resolve => setTimeout(() => resolve(3), 1000)) // 3 ]).then(alert); // 1,2,3 ما إن تجهز الوعود يُعطي كلّ واحد عنصرًا في المصفوفة
لاحِظ كيف أنّ ترتيب عناصر المصفوفة الناتجة يتطابق مع ترتيب الوعود، فحتّى لو أخذ الوعد الأوّل وقتًا أطول من غيره حتّى يُحلّ، فسيظلّ العنصر الأول في مصفوفة النواتج.
نستعمل عادةً حيلة صغيرة أن نصنع مصفوفة جديدة بخارطة تأخذ القديمة وتحوّلها إلى وعود، ثمّ نمرّر ذلك كلّه إلى Promise.all
.
فمثلًا لو لدينا مصفوفة من العناوين، يمكننا جلبها كلّها هكذا:
let urls = [ 'https://api.github.com/users/iliakan', 'https://api.github.com/users/remy', 'https://api.github.com/users/jeresig' ]; // نحوّل كلّ عنوان إلى وعد التابِع fetch let requests = urls.map(url => fetch(url)); // ينتظر Promise.all حتّى تُحلّ كلّ المهام Promise.all(requests) .then(responses => responses.forEach( response => alert(`${response.url}: ${response.status}`) ));
من أكبر الأمثلة على جلب المعلومات. هي جلب المعلومات لمجموعة من مستخدمي موقع GitHub من خلال اسمائهم (يمكننا جلب مجموعة من السلع بحسب رقم المعرف الخاص بها، منطق الحل متشابه في كِلا الحالتين):
let names = ['iliakan', 'remy', 'jeresig']; let requests = names.map(name => fetch(`https://api.github.com/users/${name}`)); Promise.all(requests) .then(responses => { // استدعيت جميع الردود بنجاح for(let response of responses) { alert(`${response.url}: ${response.status}`); // أظهر 200 من أجل كلّ عنوان url } return responses; }) // اربط مصفوفة الردود مع مصفوفة response.json() لقراءة المحتوى .then(responses => Promise.all(responses.map(r => r.json()))) // تحلل جميع الإجابات : وتكوّن مصفوفة "users" منهم .then(users => users.forEach(user => alert(user.name)));
لو رُفض أيّ وعد من الوعود، سيرفض الوعد الذي يُعيده Promise.all
مباشرةً بذلك الخطأ.
مثال:
Promise.all([ new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)), // هنا new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)), new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)) ]).catch(alert); // Error: Whoops!
نرى هنا أنّ الوعد الثاني يُرفض بعد ثانيتين، ويؤدّي ذلك إلى رفض Promise.all
كاملًا، وهكذا يُنفّذ التابِع .catch
ويصير الخطأ ناتج Promise.all
كلّه.
تحذير: في حالة حدوث خطأ ما، تُتجاهل الوعود الأُخرى لو أن وعدًا ما رُفضَ، ستُرفض جميع الوعود من خلال تابع Promise.all
مباشرةً، متناسيًا بذلك الوعود الأخرى في القائمة. وعندها ستُتجاهل نتائجها أيضًا.
على سبيل المثال، إن كان العديد من استدعاءات fetch
كما هو موضح في المثال أعلاه، وفشِل أحدها، فسيستمر تنفيذ الاستدعاءات الأخرى. ولكن لن يشاهدها تابع Promise.all
بعد الآن. ربما ستُنجز بقية الوعود، ولكن نتائجها ستُتجاهل في نهاية المطاف.
لن يُلغي التابع Promise.all
الوعود الأخرى، إذ لا يوجد مثل هذا الفعل في الوعود، سنغطي في فصل آخر طريقة المتبعة في إلغاء الوعود وسيساعدنا التابع AbortController
في ذلك، ولكنه ليس جزءًا من واجهة الوعود البرمجية.
ملاحظة: يسمح التابع Promise.all(iterable)
لغير الوعود (القيم النظامية) في iterable
. يقبل التابع Promise.all(...)
وعودًا قابلة للتكرار (مصفوفات في معظم الوقت). ولكن لو لم تكُ تلك الكائنات وعودًا. فستُمرّر النتائج كما هي.
فمثلًا، النتائج هنا [1, 2, 3]
:
Promise.all([ new Promise((resolve, reject) => { setTimeout(() => resolve(1), 1000) }), 2, 3 ]).then(alert); // 1, 2, 3
يمكننا تمرير القيم الجاهزة لتابِع Promise.all
عندما يكون ذلك مناسبًا.
Promise.allSettled
إضافة حديثة هذه إضافة حديثة للغة، وقد يحتاج إلى تعويض نقص الدعم في المتصفحات القديمة.
لو رُفض أحد الوعود في Promise.all
فسيُرفض كلّه مجتمعًا. هذا يفيدنا لو أردنا استراتيجية ”إمّا كل شيء أو لا شيء“، أي حين نطلب وجود النتائج كلها كي نواصل:
Promise.all([ fetch('/template.html'), fetch('/style.css'), fetch('/data.json') ]).then(render); // تابِع التصيير يطلب نواتج كلّ توابِع الجلب
بينما ينتظر Promise.allSettled
استقرار كلّ الوعود. للمصفوفة الناتجة هذه العناصر:
-
{status:"fulfilled", value:result}
من أجل الاستجابات الناجحة. -
{status:"rejected", reason:error}
من أجل الأخطاء.
مثال على ذلك هو لو أردنا جلب معلومات أكثر من مستخدم واحد. فلو فشل أحد الطلبات ما زلنا نريد معرفة معلومات البقية.
فلنستعمل Promise.allSettled
:
let urls = [ 'https://api.github.com/users/iliakan', 'https://api.github.com/users/remy', 'https://no-such-url' ]; Promise.allSettled(urls.map(url => fetch(url))) .then(results => { // (*) results.forEach((result, num) => { if (result.status == "fulfilled") { alert(`${urls[num]}: ${result.value.status}`); } if (result.status == "rejected") { alert(`${urls[num]}: ${result.reason}`); } }); });
ستكون قيمة results
في السطر (*)
أعلاه كالآتي:
[ {status: 'fulfilled', value: ...ردّ...}, {status: 'fulfilled', value: ...ردّ...}, {status: 'rejected', reason: ...كائن خطأ...} ]
هكذا نستلم لكلّ وعد حالته وقيمته أو الخطأ value/error
.
تعويض نقص الدعم
لو لم يكن المتصفّح يدعم Promise.allSettled
فمن السهل تعويض ذلك:
if(!Promise.allSettled) { Promise.allSettled = function(promises) { return Promise.all(promises.map(p => Promise.resolve(p).then(value => ({ state: 'fulfilled', value }), reason => ({ state: 'rejected', reason })))); }; }
في هذه الشيفرة يأخذ التابِع promises.map
قيم الإدخال ويحوّلها إلى وعود (في حال وصله شيء ليس بالوعد) ذلك باستعمال p => Promise.resolve(p)
، بعدها يُضيف دالة المُعاملة .then
لكلّ وعد.
هذه الدالة تحوّل النواتج الناجحة v
إلى {state:'fulfilled', value:v}
، والأخطاء r
إلى {state:'rejected', reason:r}
. هذا التنسيق الذي يستعمله Promise.allSettled
بالضبط.
يمكننا لآن استعمال Promise.allSettled
لجلب نتائج كلّ الوعود الممرّرة حتّى لو رفضت بعضها.
Promise.race
يشبه التابِع Promise.all
إلّا أنّه ينتظر استقرار وعد واحد فقط ويأخذ ناتجه (أو الخطأ). صياغته هي:
let promise = Promise.race(iterable);
فمثلًا سيكون الناتج هنا 1
:
Promise.race([ new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)), new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)), new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)) ]).then(alert); // 1
أوّل الوعود هنا كان أسرعها وهكذا صار هو الناتج. ومتى ”فاز أوّل وعد مستقرّ بالسباق“، تُهمل بقية النواتج/الأخطاء.
Promise.resolve/reject
نادرًا ما نستعمل Promise.resolve
و Promise.reject
في الشيفرات الحديثة إذ صياغة async/await
(نتكلم عنها في مقال لاحق)
نشرحها هنا ليكون شرحًا كاملًا، وأيضًا لمن لا يستطيع استعمال async/await
لسبب أو لآخر.
-
Promise.resolve(value)
يُنشئ وعد مُنجز بالناتجvalue
.
ويشبه:
let promise = new Promise(resolve => resolve(value));
يستخدم هذا التابع للتوافقية، عندما يُتوقع من تابع ما أن يُعيد وعدًا.
فمثلًا، يجلب التابِع أدناه loadCached
عنوان URL
ويخزن المحتوى في الذاكرة المؤقتة. ومن أجل الاستدعاءات اللاحقة لنفس عنوان URL
سيحصل على المحتوى فورًا من الذاكرة المؤقتة، ولكنه يستخدم التابع Promise.resolve
لتقديم وعدًا بهذا الأمر. لتكون القيمة المرتجعة دائمًا وعدًا:
let cache = new Map(); function loadCached(url) { if (cache.has(url)) { return Promise.resolve(cache.get(url)); // (*) } return fetch(url) .then(response => response.text()) .then(text => { cache.set(url,text); return text; }); }
يمكننا كتابة loadCached(url).then(…)
لأننا نضمن أن التابِع سيُعيد وعدًا. كما يمكننا دائمًا استخدام .then
بعد تابِع loadCached
. وهذا هو الغرض من Promise.resolve
في السطر (*)
.
Promise.reject
-
Promise.reject(error)
ينشئ وعد مرفوض مع خطأ. ويشبه:
let promise = new Promise((resolve, reject) => reject(error));
عمليًا لا نستعمل هذا التابِع أبدًا.
خلاصة
لصنف الوعود Promise
خمس توابِع ثابتة:
-
Promise.all(promises)
-- ينتظر جميع الوعود لتعمل ويعيد مصفوفة بنتائجهم. وإذا رُفض أي وعدٍ منهم، سيرجعPromise.all
خطأً، وسيتجاهلُ جميع النتائج الأخرى. -
Promise.allSettled(promises)
-- (التابع مضاف حديثًا) ينتظر جميع الوعود لتُنجز ليُعيد نتائجها كمصفوفة من الكائنات تحتوي على: -state
: الحالة وتكون إما"fulfilled"
أو"rejected"
. -value
: القيمة (إذا تحقق الوعد) أوreason
السبب (إذا رُفض). -
Promise.race(promises)
-- ينتظر الوعد الأول ليُنجز وتكون نتيجته أو الخطأ الذي سيرميه خرج هذا التابِع. -
Promise.resolve(value)
-- ينشئ وعدًا منجزًا مع القيمة الممرّرة. -
Promise.reject(error)
-- ينشئ وعدًا مرفوضًا مع الخطأ المُمرّر.
ومن بينها Promise.all
هي الأكثر استعمالًا عمليًا.
ترجمة -وبتصرف- للفصل Promise API من كتاب The JavaScript language
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.