وعود JavaScript: برنامج تعليمي بأمثلة

نشرت: 2022-03-11

تعد الوعود موضوعًا ساخنًا في دوائر تطوير JavaScript ، ويجب عليك بالتأكيد التعرف عليها. ليس من السهل لف رأسك حولها ؛ يمكن أن يستغرق الأمر بعض البرامج التعليمية والأمثلة وقدرًا لائقًا من الممارسة لفهمها.

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

تم شرح وعود JavaScript في هذا البرنامج التعليمي الشامل.

ما هو وعد JavaScript؟

الوعد هو طريقة ينتج عنها قيمة في النهاية. يمكن اعتباره النظير غير المتزامن لوظيفة getter. يمكن تفسير جوهرها على النحو التالي:

 promise.then(function(value) { // Do something with the 'value' });

يمكن أن تحل الوعود محل الاستخدام غير المتزامن لعمليات الاسترجاعات ، كما أنها توفر العديد من الفوائد عليها. لقد بدأوا في اكتساب المزيد والمزيد من المكتبات والأطر التي تتبناها باعتبارها طريقتهم الأساسية للتعامل مع عدم التزامن. Ember.js هو مثال رائع لمثل هذا الإطار.

هناك العديد من المكتبات التي تطبق مواصفات Promises / A +. سوف نتعلم المفردات الأساسية ، ونعمل من خلال بعض أمثلة جافا سكريبت الواعدة لتقديم المفاهيم الكامنة وراءها بطريقة عملية. سأستخدم إحدى مكتبات التنفيذ الأكثر شيوعًا ، rsvp.js ، في أمثلة التعليمات البرمجية.

استعد ، سنقوم برمي الكثير من النرد!

الحصول على مكتبة rsvp.js

يمكن استخدام Promises ، وبالتالي rsvp.js ، على كل من الخادم والعميل. لتثبيته لـ nodejs ، انتقل إلى مجلد المشروع واكتب:

 npm install --save rsvp

إذا كنت تعمل على الواجهة الأمامية وتستخدم التعريشة ، فهي مجرد ملف

 bower install -S rsvp

بعيد.

إذا كنت ترغب فقط في الوصول إلى اللعبة بشكل صحيح ، فيمكنك تضمينها عبر علامة نصية بسيطة (ومع jsbin ، يمكنك إضافتها عبر القائمة المنسدلة "إضافة مكتبة"):

 <script src="//cdn.jsdelivr.net/rsvp/3.0.6/rsvp.js"></script>

ما هي خصائص الوعد؟

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

طريقة إنشاء الوعد في rsvp.js هي عبر ما يسمى المُنشئ الكاشِف. يأخذ هذا النوع من المُنشئ معلمة دالة واحدة ويستدعيها فورًا مع وسيطتين ، fulfill reject ، والتي يمكن أن تنقل fulfilled إلى الحالة المستوفاة أو rejected :

 var promise = new RSVP.Promise(function(fulfill, reject) { (...) });

يُطلق على نمط وعود JavaScript هذا مُنشئ كاشِف لأن وسيطة دالة واحدة تكشف عن قدراتها لوظيفة المُنشئ ، ولكنها تضمن أن مستهلكي الوعد لا يمكنهم التلاعب بحالته.

يمكن لمستهلكي الوعد أن يتفاعلوا مع تغييرات حالته من خلال إضافة معالجهم من خلال طريقة then . يتطلب الأمر وظيفة معالج الرفض والوفاء ، وكلاهما يمكن أن يكون مفقودًا.

 promise.then(onFulfilled, onRejected);

اعتمادًا على نتيجة عملية حل الوعد ، يتم استدعاء معالج onFulfilled أو المعالج onRejected بشكل غير متزامن .

دعنا نرى مثالاً يوضح ترتيب تنفيذ الأشياء:

 function dieToss() { return Math.floor(Math.random() * 6) + 1; } console.log('1'); var promise = new RSVP.Promise(function(fulfill, reject) { var n = dieToss(); if (n === 6) { fulfill(n); } else { reject(n); } console.log('2'); }); promise.then(function(toss) { console.log('Yay, threw a ' + toss + '.'); }, function(toss) { console.log('Oh, noes, threw a ' + toss + '.'); }); console.log('3');

يطبع هذا المقتطف إخراجًا مشابهًا لما يلي:

 1 2 3 Oh, noes, threw a 4.

أو ، إذا حالفنا الحظ ، نرى:

 1 2 3 Yay, threw a 6.

يوضح هذا البرنامج التعليمي الوعود شيئين.

أولاً ، أن المعالجات التي أرفقناها بالوعد تم استدعاؤها بالفعل بعد تشغيل جميع التعليمات البرمجية الأخرى ، بشكل غير متزامن.

ثانيًا ، أنه تم استدعاء معالج الإنجاز فقط عند الوفاء بالوعد ، مع القيمة التي تم حلها بها (في حالتنا ، نتيجة رمي النرد). وينطبق الشيء نفسه على معالج الرفض.

تقيد بالوعود وتقلصها

تتطلب المواصفات أن الدالة then (المعالجات) يجب أن ترجع وعدًا أيضًا ، مما يتيح ربط الوعود معًا ، مما ينتج عنه رمز يبدو متزامنًا تقريبًا:

 signupPayingUser .then(displayHoorayMessage) .then(queueWelcomeEmail) .then(queueHandwrittenPostcard) .then(redirectToThankYouPage)

هنا ، يُرجع signupPayingUser وعدًا ، ويتم استدعاء كل وظيفة في سلسلة الوعد بالقيمة المرجعة للمعالج السابق بمجرد اكتمالها. لجميع الأغراض العملية ، يؤدي هذا إلى إجراء تسلسل للمكالمات دون حظر سلسلة التنفيذ الرئيسية.

لنرى كيف يتم حل كل وعد مع القيمة المعادة للعنصر السابق في السلسلة ، نعود إلى رمي النرد. نريد رمي النرد ثلاث مرات كحد أقصى ، أو حتى ظهور الستة الأولى jsbin:

 function dieToss() { return Math.floor(Math.random() * 6) + 1; } function tossASix() { return new RSVP.Promise(function(fulfill, reject) { var n = Math.floor(Math.random() * 6) + 1; if (n === 6) { fulfill(n); } else { reject(n); } }); } function logAndTossAgain(toss) { console.log("Tossed a " + toss + ", need to try again."); return tossASix(); } function logSuccess(toss) { console.log("Yay, managed to toss a " + toss + "."); } function logFailure(toss) { console.log("Tossed a " + toss + ". Too bad, couldn't roll a six"); } tossASix() .then(null, logAndTossAgain) //Roll first time .then(null, logAndTossAgain) //Roll second time .then(logSuccess, logFailure); //Roll third and last time

عندما تقوم بتشغيل رمز وعود هذا ، سترى شيئًا كهذا على وحدة التحكم:

 Tossed a 2, need to try again. Tossed a 1, need to try again. Tossed a 4. Too bad, couldn't roll a six.

الوعد الذي تم إرجاعه بواسطة tossASix يتم رفضه عندما لا يكون القرعة ستة ، لذلك يتم استدعاء معالج الرفض مع الإرم الفعلي. logAndTossAgain يطبع النتيجة على وحدة التحكم ويعيد وعدًا يمثل رمية نرد أخرى. هذا logAndTossAgain ، بدوره ، يتم رفضه وتسجيل الخروج من خلال السجل التالي مرة أخرى.

ومع ذلك ، في بعض الأحيان ، تكون محظوظًا * ، وتمكنك من جمع ستة:

 Tossed a 4, need to try again. Yay, managed to toss a 6.

* ليس عليك أن تكون محظوظًا. هناك فرصة بنسبة 42٪ لرمي ستة أحجار على الأقل إذا رميت ثلاثة أحجار نرد.

هذا المثال يعلمنا أيضًا شيئًا أكثر. انظر كيف لم يتم إجراء المزيد من الرميات بعد أول دحرجة ناجحة لستة؟ لاحظ أن جميع معالجات التنفيذ (الوسيطات الأولى في استدعاءات then ) في السلسلة null ، باستثناء آخرها ، logSuccess . تتطلب المواصفات أنه إذا لم يكن المعالج (الوفاء أو الرفض) دالة ، فيجب حل الوعد المرتجع (تم الوفاء به أو رفضه) بنفس القيمة. في مثال الوعود أعلاه ، لا يعد معالج الوفاء ، null ، دالة وقد تم الوفاء بقيمة الوعد بـ 6. لذا فإن الوعد الذي تم إرجاعه من خلال المكالمة في then (التالي في السلسلة) سيتم الوفاء به أيضًا مع 6 قيمة.

يتكرر هذا حتى يتوفر معالج الإنجاز الفعلي (أحد الوظائف) ، وبالتالي يتدفق التنفيذ إلى أسفل حتى يتم التعامل معه. في حالتنا ، يحدث هذا في نهاية السلسلة حيث يتم تسجيل الخروج بمرح على وحدة التحكم.

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

تتطلب مواصفات الوعود / A + أنه إذا تم رفض الوعد أو تم إلقاء خطأ في معالج الرفض ، فيجب معالجته بواسطة معالج الرفض "المصب" من المصدر.

توفر الاستفادة من التقنية المتقطعة أدناه طريقة نظيفة للتعامل مع الأخطاء:

 signupPayingUser .then(displayHoorayMessage) .then(queueWelcomeEmail) .then(queueHandwrittenPostcard) .then(redirectToThankYouPage) .then(null, displayAndSendErrorReport)

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

دعنا نعود إلى النرد الحبيب ونرى ذلك في العمل. لنفترض أننا نريد فقط رمي النرد بشكل غير متزامن وطباعة النتائج:

 var tossTable = { 1: 'one', 2: 'two', 3: 'three', 4: 'four', 5: 'five', 6: 'six' }; function toss() { return new RSVP.Promise(function(fulfill, reject) { var n = Math.floor(Math.random() * 6) + 1; fulfill(n); }); } function logAndTossAgain(toss) { var tossWord = tossTable[toss]; console.log("Tossed a " + tossWord.toUppercase() + "."); } toss() .then(logAndTossAgain) .then(logAndTossAgain) .then(logAndTossAgain);

عندما تقوم بتشغيل هذا ، لا يحدث شيء. لم تتم طباعة أي شيء على وحدة التحكم ولم يتم إلقاء أية أخطاء ، على ما يبدو.

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

 function logAndTossAgain(toss) { var tossWord = tossTable[toss]; console.log("Tossed a " + tossWord.toUpperCase() + "."); } function logErrorMessage(error) { console.log("Oops: " + error.message); } toss() .then(logAndTossAgain) .then(logAndTossAgain) .then(logAndTossAgain) .then(null, logErrorMessage);

يؤدي تشغيل الكود أعلاه إلى إظهار الخطأ الآن:

 "Tossed a TWO." "Oops: Cannot read property 'toUpperCase' of undefined"

لقد نسينا إرجاع شيء ما من logAndTossAgain ويتم الوفاء بالوعد الثاني مع undefined . ثم ينفجر معالج التنفيذ التالي في محاولة للاتصال بـ toUpperCase على ذلك. هذا شيء مهم آخر يجب تذكره: قم دائمًا بإرجاع شيء ما من المتعاملين ، أو كن مستعدًا في معالجات لاحقة لعدم تجاوز أي شيء.

بناء أعلى

لقد رأينا الآن أساسيات وعود JavaScript في مثال التعليمات البرمجية في هذا البرنامج التعليمي. من الفوائد الكبيرة لاستخدامها أنه يمكن تكوينها بطرق بسيطة لإنتاج وعود "مركبة" مع السلوك الذي نرغب فيه. توفر مكتبة rsvp.js عددًا قليلاً منها ، ويمكنك دائمًا إنشاء الخاصة بك باستخدام الأساسيات وتلك ذات المستوى الأعلى.

بالنسبة للمثال الأخير والأكثر تعقيدًا ، نسافر إلى عالم لعب الأدوار AD&D ورمي النرد للحصول على درجات الشخصية. يتم الحصول على هذه الدرجات من خلال دحرجة ثلاثة أحجار نرد لكل مهارة للشخصية.

دعني ألصق الكود هنا أولاً ثم أشرح ما هو الجديد:

 function toss() { var n = Math.floor(Math.random() * 6) + 1; return new RSVP.resolve(n); // [1] } function threeDice() { var tosses = []; function add(x, y) { return x + y; } for (var i=0; i<3; i++) { tosses.push(toss()); } return RSVP.all(tosses).then(function(results) { // [2] return results.reduce(add); // [3] }); } function logResults(result) { console.log("Rolled " + result + " with three dice."); } function logErrorMessage(error) { console.log("Oops: " + error.message); } threeDice() .then(logResults) .then(null, logErrorMessage);

نحن على دراية toss من مثال الكود الأخير. إنه ببساطة يخلق وعدًا يتم الوفاء به دائمًا نتيجة إلقاء النرد. لقد استخدمت RSVP.resolve ، وهي طريقة مناسبة تخلق مثل هذا الوعد مع احتفال أقل (انظر [1] في الكود أعلاه).

في threeDice ، قمت بإنشاء 3 وعود يمثل كل منها رمي نرد وأخيراً جمعتها مع RSVP.all . تأخذ RSVP.all مجموعة من الوعود ويتم حلها بمجموعة من قيمها المحددة ، واحدة لكل وعد مكون ، مع الحفاظ على نظامها. هذا يعني أن لدينا نتيجة القذف في results (انظر [2] في الكود أعلاه) ، ونعيد الوعد الذي تم الوفاء به بمجموعها (انظر [3] في الكود أعلاه).

حل الوعد الناتج ثم تسجيل العدد الإجمالي:

 "Rolled 11 with three dice"

استخدام الوعود لحل مشاكل حقيقية

تُستخدم وعود JavaScript لحل المشكلات في التطبيقات الأكثر تعقيدًا بكثير من عمليات رمي ​​النرد غير المتزامنة بدون سبب وجيه .

إذا استبدلت رمي ​​ثلاثة أحجار نرد بإرسال ثلاثة طلبات من أجاكس لفصل نقاط النهاية والمتابعة عند عودة كل منهم بنجاح (أو إذا فشل أي منها) ، فلديك بالفعل تطبيق مفيد للوعود و RSVP.all .

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

بالكاد خدشنا سطح ما يمكن أن تفعله الوعود في هذا البرنامج التعليمي لجافا سكريبت. توفر مكتبات Promise مجموعة جيدة من الأساليب والمنشآت منخفضة المستوى تحت تصرفك. أتقن هؤلاء ، والسماء هي الحد الأقصى لما يمكنك فعله معهم.

عن المؤلف

كان Balint Erdi من محبي لعب الأدوار الرائع ومشجع AD&D منذ وقت طويل ، وهو وعد كبير ومشجع Ember.js الآن. ما كان ثابتًا هو شغفه بموسيقى الروك أند رول. لهذا السبب قرر كتابة كتاب على Ember.js يستخدم موسيقى الروك أند رول كموضوع للتطبيق في الكتاب. قم بالتسجيل هنا لتعرف متى يتم إطلاقه.