Promise در جاوا اسکریپت و کاربردهای آن — به زبان ساده
برخی از برنامهنویسان رابطه عشق و نفرت توأمان با جاوا اسکریپت دارند. اما در هر صورت جاوا اسکریپت همواره جذاب است. برای کسانی که به طور عمده روی جاوا و PHP کار کرده باشند، جاوا اسکریپت ممکن است بسیار متفاوت به نظر بیاید. در این نوشته به بررسی موضوع Promise در جاوا اسکریپت میپردازیم که یکی از جذابترین موضوعات در این زبان برنامهنویسی محسوب میشود.
به طور مکرر میشنویم که برخی افراد میگویند با بهرهگیری از Promise میتوانید از شر callback-ها در جاوا اسکریپت رها شوید. با این که این واقعیت ممکن است یکی از مزیتهای Promise باشد، اما نکات بسیار بیشتری در خصوص Promise وجود دارد که باید بدانیم. در این مقاله تلاش کردهایم برخی از این موارد را مورد بحث و بررسی قرار دهیم.
مقدمه
زمانی که برای نخستین بار میخواهید روی جاوا اسکریپت کار کنید، اوضاع کمی ترسناک به نظر میرسد. میشنوید که برخی افراد میگویند جاوا اسکریپت یک زبان برنامهنویسی «همگام» (synchronous) است، در حالی که برخی دیگر میگویند «ناهمگام» (asynchronous) است. همچنین با عبارتهای کد مسدودکننده، کد غیر مسدودکننده، الگوی طراحی رویداد-محور، چرخه عمر رویداد، پشته تابع، صف رویداد، bubbling ،polyfill ،babel ،angular ،ReactJS ،vue JS، و بسیاری از ابزارها و کتابخانههای دیگر مواجه میشوید.
البته نباید بترسید، چون این تجربه صرفاً برای شما رخ نداده و همه افرادی که اولین بار با این زبان آشنا میشوند، همین تجربه را از سر گذراندهاند. این حالت به نام «خستگی جاوا اسکریپت» (JavaScript Fatigue) نامیده میشود. «کُری هاوس» این وضعیت را در توییت زیر به این صورت توصیف کرده است:
خستگی جاوا زمانی اتفاق میافتد که افراد از ابزارهایی که لازم ندارند، برای حل مشکلاتی که ندارند، استفاده میکنند.
جاوا اسکریپت یک زبان برنامهنویسی همگام است؛ اما به لطف تابعهای callback میتوان آن را به شکل یک زبان برنامهنویسی ناهمگام نیز درآورد.
Promise به زبان خیلی ساده
Promise در لغت به معنی «قول» و «قرار» است و در جاوا اسکریپت نیز معنایی کاملاً مشابه این دارد. بنابراین ابتدا به این میپردازیم که قول در زندگی روزمره به چه معنا است. تعریف قول در دیکشنری به شرح زیر است:
قول: تضمین این که فردی کاری را انجام خواهد داد و یا اتفاق خاصی خواهد افتاد.
بنابراین زمانی که فردی به شما قولی میدهد، چه اتفاقی میافتد؟
- این قول به شما اطمینان میدهد که کاری انجام خواهد شد، چه فردی که قول داده آن کار را شخصاً انجام دهد و یا اجرای آن را به دیگران محول کند. در هر صورت یک اطمینان به شما داده میشود که میتوانید بر مبنای آن برنامهریزی کنید.
- یک قول میتواند حفظ شود یا شکسته شود.
- زمانی که یک قول حفظ میشود شما انتظار دارید که یک خروجی به دست آید. شما میتوانید از این خروجی برای کارها یا طرحهای بعدی خود استفاده کید.
- زمانی که یک قول میشکند، احتمالاً میخواهید بدانید که چرا فرد قول دهنده نتوانسته است به آن وفادار بماند. زمانی که دلیل این مسئله را دانستید و مطمئن شدید که قول شکسته است میتوانید در ادامه برنامهریزی کنید که برای مدیریت این وضعیت چه کاری باید انجام شود.
- در زمانی که قولی داده میشود، ما تنها یک اطمینان به دست میآوریم. ما نمیتوانیم بیدرنگ بر مبنای آن کاری را انجام دهیم. ما زمانی میتوانیم موارد موردنیاز خود را فرمولبندی و یا اجرا کنیم که قول عمل شده باشد (و خروجی به دست آید) یا قول بشکند (و با دانستن دلیل آن برای وضعیت شکست برنامهریزی کنیم).
- این احتمال وجود دارد که هیچ خبری از کسی که قول داده بود به دست نیاید. در چنین مواردی، احتمالاً برای خود یک آستانه زمانی تعیین میکنید. فرض کنید مثلاً میگویید اگر شخصی که قول داده است تا 10 روز پیدا نشود، متوجه میشویم که مشکلی برای وی پیش آمده و دیگر نمیتواند به قول خود عمل کند. بنابراین حتی اگر شخص پس از 15 روز نزد شما بیاید، دیگر اهمیتی ندارد، زیرا شما قبلاً برنامههای جایگزین خود را اجرا کردهاید.
Promise در جاوا اسکریپت
در زمانی که با زبان برنامهنویسی جاوا اسکریپت کار میکنید، بهتر است همواره مستندات وب MDN را مطالعه کنید. این منابع جزییات بسیار خوبی را ارائه میکنند. بنابراین هم اینک و قبل از هر کار دیگر، ابتدا صفحه مستندات Promise (+) را در MDN مطالعه کنید.
برای درک Promise-ها دو بخش وجود دارد که یکی ایجاد promise و دیگری مدیریت promise است. با این که اغلب کدهای ما به طور کلی به مدیریت promise-های ایجادشده از سوی کتابخانههای دیگر مرتبط است؛ اما داشتن درک کاملی از فرایند ایجاد آن نیز مطمئناً کمک زیادی به ما میکند. درک ایجاد promise برای عبور از سطح یک برنامهنویس مبتدی به پیشرفته کاملاً ضروری است.
ایجاد promise
به امضای متد زیر برای ایجاد یک promise جدید توجه کنید:
new Promise(/* executor */ function(resolve, reject) { ... });
این سازنده یک تابع به نام executor را میپذیرد. این تابع executor دو پارامتر به نامهای resolve و reject قبول میکند که آنها نیز خود تابع هستند. promise-ها به طور کلی برای مدیریت آسانتر عملیات ناهمگام یا کدهای مسدودکننده استفاده میشوند. نمونههایی از این کدها را میتوان در عملیات فایل، فراخوانی API، فراخوانی پایگاه داده، فراخوانیهای IO و موارد دیگر مشاهده کرد.
مقداردهی اولیه این عملیات ناهمگام درون تابع executor رخ میدهد. اگر مجموعه عملیات ناهمگام موفق باشند، در این صورت، نتیجه موردِ انتظار با فراخوانی تابع resolve از سوی ایجادکننده promise بازگشت مییابد. به طور مشابه، اگر نوعی خطای غیرمنتظره رخ بدهد، دلایل با فراخوانی تابع reject ارسال میشود.
اینک که میدانیم چگونه یک promise بسازیم، میتوانیم یک promise ساده را به منظور درک بهتر خود ایجاد کنیم.
1var keepsHisWord;
2keepsHisWord = true;
3promise1 = new Promise(function(resolve, reject) {
4 if (keepsHisWord) {
5 resolve("The man likes to keep his word");
6 } else {
7 reject("The man doesnt want to keep his word");
8 }
9});
10console.log(promise1);
از آنجا که این promise هم اینک resolve شده است، ما قادر نخواهیم بود حالت ابتدایی promise را مورد بازبینی قرار دهیم. بنابراین اجازه بدهید یک promise جدید ایجاد کنیم که resolve کردن آن به مقداری زمان نیاز داشته باشد. سادهترین روش برای این کار استفاده از تابع setTimeOut است.
1promise2 = new Promise(function(resolve, reject) {
2 setTimeout(function() {
3 resolve({
4 message: "The man likes to keep his word",
5 code: "aManKeepsHisWord"
6 });
7 }, 10 * 1000);
8});
9console.log(promise2);
کد فوق یک promise ایجاد میکند که به صورت نامشروطی پس از 10 ثانیه resolve میشود. بدین ترتیب میتوانیم حالت promise را تا زمانی که هنوز resolve نشده است، مورد بررسی قرار دهیم.
زمانی که 10 ثانیه از آغاز promise سپری شود، resolve خواهد شد. PromiseStatus و همچنین PromiseValue بر همین اساس بهروزرسانی میشوند. همان طور که میبینید ما تابع resolve را طوری بهروزرسانی کردهایم که یک شیء JSON به جای یک رشته ساده بازگشت دهد. این کار صرفاً به این منظور صورت گرفته است که نشان دهیم میتوانیم در تابع resolve مقادیر دیگری را نیز بازگشت دهیم.
اینک نگاهی به promise-هایی میاندازیم که reject میشوند. بدین منظور کد promise فوق را کمی تغییر داده و به صورت زیر درمیآوریم:
1keepsHisWord = false;
2promise3 = new Promise(function(resolve, reject) {
3 if (keepsHisWord) {
4 resolve("The man likes to keep his word");
5 } else {
6 reject("The man doesn't want to keep his word");
7 }
8});
9console.log(promise3);
از آنجا که ما یک rejection مدیریت نشده ایجاد کردهایم، مرورگر کروم یک خطا نمایش میدهد. فعلاً میتوانید این خطا را نادیده بگیرید. در ادامه آن را نیز بررسی خواهیم کرد.
همان طور که میبینید PromiseStatus میتواند سه مقدار متفاوت داشته باشد که pending resolved یا rejected هستند. زمانی که promise یک PromiseStatus ایجاد میکند، ابتدا در حالت pending قرار دارد و PromiseValue به صورت undefended خواهد بود، تا این که promise به صورت resolve یا reject در آید. زمانی که یک promise در حالت resolve یا reject در آید گفته میشود که تعیین تکلیف (settled) شده است. بنابراین یک promise به طور کلی گذار از حالت pending به settled است.
اکنون که میدانیم promise-ها چگونه ایجاد میشوند، میتوانیم به بررسی شیوه استفاده یا مدیریت آنها بپردازیم. این مسئله باید با درک شیء promise همراه باشد.
درک شیء promise
شیء peomise بر اساس مستندات MDN به صورت زیر تعریف شده است:
- شیء promise نشاندهنده تکمیل (یا شکست) نهایی یک عملیات ناهمگام و مقدار حاصل است.
شیء promise دارای متدهای استاتیک و «متدهای پروتوتایپ» (prototype methods) است متدهای استاتیک در شیء promise میتوانند به صورت مستقل استفاده شوند در حالی که متدهای پروتوتایپ باید روی وهلههایی از شیء promise اعمال شوند. به خاطر داشته باشید که هم متدهای نرمال و هم پروتوتایپها همگی promise بازگشت میدهند و بدین ترتیب بهتر میتوانید معنی همه چیز را درک کنید.
متدهای پروتوتایپ
سه نوع متد پروتوتایپ وجود دارد. به خاطر داشته باشید که همه این متدها میتوانند روی یک وهله از شیء promise اعمال شوند و این متدها به نوبه خود یک promise بازگشت میدهند. همه متدهای بعدی «دستگیره» (handler)-هایی برای گذار حالتهای مختلف یک promise اختصاص میدهند. همان طور که پیشتر دیدیم، زمانی که یک promise ایجاد میشود، در حالت pending قرار دارد. زمانی که promise تعیین وضعیت میشود، برحسب این که موفق بوده یا شکست خورده باشد، یک یا چند مورد از سه متد زیر اجرا خواهند شد:
Promise.prototype.catch(onRejected) Promise.prototype.then(onFulfilled, onRejected) Promise.prototype.finally(onFinally)
در تصویر زیر گردش کار متدهای.then و.catch را مشاهده میکنید. از آنجا که این متدها یک promise بازگشت میدهند، میتوان آنها را به هم متصل ساخت که این وضعیت در تصویر نیز نمایش یافته است. اگر.ginally برای یک promise اعلان شده باشد، در این صورت هر زمان که promise تعیین وضعیت شود، اجرا خواهد شد و مهم نیست که promise موفق بوده یا شکست خورده باشد. البته متد finally از پشتیبانی کمی برخوردار است و از این رو قبل از استفاده از آن باید مورد به مورد بررسی صورت بگیرد.
تصور کنید یک کودک دبستانی از مادر خود یک گوشی تلفن همراه میخواهد. مادرش میگوید: «من آخر این ماه یک تلفن برای تو خواهم خرید.»
در ادامه کد این داستان را در جاوا اسکریپت در حالتی که قول مادر در انتهای ماه اجرا شود یا نشود مشاهده میکنید:
1var momsPromise = new Promise(function(resolve, reject) {
2 momsSavings = 20000;
3 priceOfPhone = 60000;
4 if (momsSavings > priceOfPhone) {
5 resolve({
6 brand: "iphone",
7 model: "6s"
8 });
9 } else {
10 reject("We donot have enough savings. Let us save some more money.");
11 }
12});
13momsPromise.then(function(value) {
14 console.log("Hurray I got this phone as a gift ", JSON.stringify(value));
15});
16momsPromise.catch(function(reason) {
17 console.log("Mom coudn't buy me the phone because ", reason);
18});
19momsPromise.finally(function() {
20 console.log(
21 "Irrespecitve of whether my mom can buy me a phone or not, I still love her"
22 );
23});
خروجی کد فوق به صورت زیر خواهد بود:
اگر مقدار momsSavings را به 200000 تغییر دهیم، در این صورت مادر میتواند هدیه پسرش را بخرد. در چنین حالتی خروجی به صورت زیر خواهد بود:
حال خود را جای کسی میگذاریم که میخواهد از این کتابخانه استفاده کند. ما خروجی را تقلید میکنیم و بدین ترتیب میتوانیم با شیوه استفاده از آن و سپس استفاده کارآمد از آن آشنا شویم.
از آنجا که then. میتواند به دستگیرههای onFulfilled و onRejected متصل شود، به جای نوشتن then. و catch. مجزا، میتوانیم هر دو را در then. بنویسیم و کد به صورت زیر درمیآید:
1momsPromise.then(
2 function(value) {
3 console.log("Hurray I got this phone as a gift ", JSON.stringify(value));
4 },
5 function(reason) {
6 console.log("Mom coudn't buy me the phone because ", reason);
7 }
8);
اما برای افزایش خوانایی کد، بهتر است آنها را جدا از هم نگه داریم.
برای این که مطمئن شویم که میتوانیم همه این نمونهها را در مرورگر به طور کلی و یا در مرورگر کروم به طور خاص اجرا کنیم، باید اطمینان حاصل کنیم که وابستگیهای خارجی در نمونه کدهای خود نداریم. برای دستیابی به درک بهتری از موضوعات بیشتر یک تابع ایجاد میکنیم که promise بازگشتی آن به صورت تصادفی resolve یا reject میشود و بدین ترتیب میتوانیم سناریوهای مختلف را بیازماییم. برای درک بهتر مفهوم تابعهای ناهمگام یک تأخیر تصادفی را نیز در تابع خود تعریف میکنیم. از آنجا که به اعداد تصادفی نیاز داریم ابتدا باید یک تابع تصادفی ایجاد کنیم که عددی بین x و y بازگشت میدهد:
1function getRandomNumber(start = 1, end = 10) {
2 //works when both start,end are >=1 and end > start
3 return parseInt(Math.random() * end) % (end-start+1) + start;
4}
فرض کنید تابعی میسازیم که یک promise به ما بازمیگرداند. فرض کنید تابع خود را promiseTRRARNOSG بنامیم که اختصاری برای عبارت زیر است:
promiseThatResolvesRandomlyAfterRandomNumnberOfSecondsGenerator
این تابع یک promise ایجاد میکند که پس از مدت زمانی تصادفی بین 2 تا 10 ثانیه به صورت تصادفی یک نتیجه resolve یا reject ایجاد میکند. برای تصادفی ساختن موفقیت یا شکست نتیجه نیز یک تابع تصادفی بین اعداد 1 تا 10 ایجاد میکنیم. اگر عدد تصادفی کمتر از 5 باشد، promise به صورت resolve و در غیر این صورت reject خواهد شد.
1function getRandomNumber(start = 1, end = 10) {
2 //works when both start and end are >=1
3 return (parseInt(Math.random() * end) % (end - start + 1)) + start;
4}
5var promiseTRRARNOSG = (promiseThatResolvesRandomlyAfterRandomNumnberOfSecondsGenerator = function() {
6 return new Promise(function(resolve, reject) {
7 let randomNumberOfSeconds = getRandomNumber(2, 10);
8 setTimeout(function() {
9 let randomiseResolving = getRandomNumber(1, 10);
10 if (randomiseResolving > 5) {
11 resolve({
12 randomNumberOfSeconds: randomNumberOfSeconds,
13 randomiseResolving: randomiseResolving
14 });
15 } else {
16 reject({
17 randomNumberOfSeconds: randomNumberOfSeconds,
18 randomiseResolving: randomiseResolving
19 });
20 }
21 }, randomNumberOfSeconds * 1000);
22 });
23});
24var testProimse = promiseTRRARNOSG();
25testProimse.then(function(value) {
26 console.log("Value when promise is resolved : ", value);
27});
28testProimse.catch(function(reason) {
29 console.log("Reason when promise is rejected : ", reason);
30});
31// Let us loop through and create ten different promises using the function to see some variation. Some will be resolved and some will be rejected.
32for (i=1; i<=10; i++) {
33 let promise = promiseTRRARNOSG();
34 promise.then(function(value) {
35 console.log("Value when promise is resolved : ", value);
36 });
37 promise.catch(function(reason) {
38 console.log("Reason when promise is rejected : ", reason);
39 });
40}
اینک صفحه مرورگر را رفرش کنید و کد را در کنسول اجرا کنید تا خروجیهای مختلف سناریوهای resolve و reject را ملاحظه کنید. در ادامه بررسی میکنیم که چگونه میتوان promise-های مختلف ایجاد کرد و خروجیهای آنها را بدون نیاز به این کارها بررسی نمود.
متدهای استاتیک
چهار متد استاتیک در شیء Promise وجود دارند. دو مورد نخست متدهای کمکی یا میانبر هستند. این موارد به ایجاد promise-های resolve یا reject به روشی آسان کمک میکنند.
1Promise.reject(reason)
بدین ترتیب یک promise به صورت reject ایجاد میشود.
1var promise3 = Promise.reject("Not interested");
2promise3.then(function(value){
3 console.log("This will not run as it is a resolved promise. The resolved value is ", value);
4});
5promise3.catch(function(reason){
6 console.log("This run as it is a rejected promise. The reason is ", reason);
7});
1Promise.resolve(value)
یک promise به صورت resolve ایجاد میکند.
1var promise4 = Promise.resolve(1);
2promise4.then(function(value){
3 console.log("This will run as it is a resovled promise. The resolved value is ", value);
4});
5promise4.catch(function(reason){
6 console.log("This will not run as it is a resolved promise", reason);
7});
دقت کنید که یک pomise چند دستگیره دارد. بنابراین میتوانید کد را به صورت زیر بهروزرسانی کنید:
1var promise4 = Promise.resolve(1);
2promise4.then(function(value){
3 console.log("This will run as it is a resovled promise. The resolved value is ", value);
4});
5promise4.then(function(value){
6 console.log("This will also run as multiple handlers can be added. Printing twice the resolved value which is ", value * 2);
7});
8promise4.catch(function(reason){
9 console.log("This will not run as it is a resolved promise", reason);
10});
و خروجی مانند زیر خواهد بود:
دو متد بعدی به پردازش یک مجموعه از promise-ها کمک میکنند. زمانی که با چندین promise سر و کار دارید، بهتر است که ابتدا یک آرایه از promise-ها ایجاد کنید و سپس اقدامات ضروری را روی مجموعه promise-ها اجرا نمایید. برای درک این متدها نمیتوان از promiseTRRARNOSG استفاده کرد، زیرا بیش از حد تصادفی است. بهتر است برخی promise-های با قطعیت بیشتر داشته باشیم تا بتوانیم رفتار آنها را بهتر درک کنیم. در ادامه دو تابع ایجاد میکنیم. یکی از آنها پس از n ثانیه resolve میشود و دیگری پس از n ثانیه reject خواهد شد.
1var promiseTRSANSG = (promiseThatResolvesAfterNSecondsGenerator = function(
2 n = 0
3) {
4 return new Promise(function(resolve, reject) {
5 setTimeout(function() {
6 resolve({
7 resolvedAfterNSeconds: n
8 });
9 }, n * 1000);
10 });
11});
12var promiseTRJANSG = (promiseThatRejectsAfterNSecondsGenerator = function(
13 n = 0
14) {
15 return new Promise(function(resolve, reject) {
16 setTimeout(function() {
17 reject({
18 rejectedAfterNSeconds: n
19 });
20 }, n * 1000);
21 });
22});
اینک این تابعهای کمکی را برای درک بهتر Promise.All مورد استفاده قرار میدهیم.
Promise.All
بر اساس مستندات MDN تعریف Promise.All چنین است:
متد (Promise.All(iterable یک promise منفرد بازمیگرداند که وقتی همه promise-ها در آرگومان itrable به صورت resolve درآیند، یا زمانی که آرگومان iterable شامل هیچ promise نباشد، resolve میشود. این متد زمانی reject میشود که promise اول reject شود.
حالت اول
همه promise-ها به صورت resolve درآیند. این وضعیت سناریوی با بیشترین فراوانی است.
1console.time("Promise.All");
2var promisesArray = [];
3promisesArray.push(promiseTRSANSG(1));
4promisesArray.push(promiseTRSANSG(4));
5promisesArray.push(promiseTRSANSG(2));
6var handleAllPromises = Promise.all(promisesArray);
7handleAllPromises.then(function(values) {
8 console.timeEnd("Promise.All");
9 console.log("All the promises are resolved", values);
10});
11handleAllPromises.catch(function(reason) {
12 console.log("One of the promises failed with the following reason", reason);
13});
دو مشاهده مهم وجود دارند که باید به طور کلی از خروجی به دست آوریم.
- مشاهده اول: promise سوم که 2 ثانیه طول میکشد، پیش از promise سوم که 4 ثانیه طول میکشد، به پایان میرسد. اما همان طور که در خروجی میبینید، ترتیب promise-ها در مقادیر حفظ شده است.
- مشاهده دوم: یک تایمر کنسول اضافه کردهایم تا بدانیم که Promise.All چه قدر طول میکشد. اگر promise-ها به صورت ترتیبی اجرا شوند، این زمان مجموعاً باید 7=2+4+1 طول بکشد. اما از تایمر ما مشخص است که این فرایند تنها 4 ثانیه طول میکشد. این اثباتی است بر این نکته که promise-ها به صورت موازی اجرا شدهاند.
حالت دوم
زمانی که هیچ promise-ی وجود ندارد. این حالت کمترین فراوانی را دارد.
1console.time("Promise.All");
2var promisesArray = [];
3promisesArray.push(1);
4promisesArray.push(4);
5promisesArray.push(2);
6var handleAllPromises = Promise.all(promisesArray);
7handleAllPromises.then(function(values) {
8 console.timeEnd("Promise.All");
9 console.log("All the promises are resolved", values);
10});
11handleAllPromises.catch(function(reason) {
12 console.log("One of the promises failed with the following reason", reason);
13});
از آنجا که هیچ promise-ی در آرایه وجود ندارد، promise بازگشتی به صورت resolve خواهد بود.
حالت سوم
به همان دلیلی که promise اول ریجکت شده است، ریجکت میشود.
1console.time("Promise.All");
2var promisesArray = [];
3promisesArray.push(promiseTRSANSG(1));
4promisesArray.push(promiseTRSANSG(5));
5promisesArray.push(promiseTRSANSG(3));
6promisesArray.push(promiseTRJANSG(2));
7promisesArray.push(promiseTRSANSG(4));
8var handleAllPromises = Promise.all(promisesArray);
9handleAllPromises.then(function(values) {
10 console.timeEnd("Promise.All");
11 console.log("All the promises are resolved", values);
12});
13handleAllPromises.catch(function(reason) {
14 console.timeEnd("Promise.All");
15 console.log("One of the promises failed with the following reason ", reason);
16});
Promise.race
بر اساس مستندات MDN، متد Promise.race به صورت زیر تعریفشده است:
متد (Promise.race(iterable یک promise بازگشت میدهد که به محض این که یکی از promise-ها در iterable به صورت resolve یا reject در آید، یک promise به صورت موفق یا شکست خورده بازگشت میدهد که مقدار یا نتیجه promise را در خود دارد.
حالت اول
یکی از promise-ها اول resolve میشود.
1console.time("Promise.race");
2var promisesArray = [];
3promisesArray.push(promiseTRSANSG(4));
4promisesArray.push(promiseTRSANSG(3));
5promisesArray.push(promiseTRSANSG(2));
6promisesArray.push(promiseTRJANSG(3));
7promisesArray.push(promiseTRSANSG(4));
8var promisesRace = Promise.race(promisesArray);
9promisesRace.then(function(values) {
10 console.timeEnd("Promise.race");
11 console.log("The fasted promise resolved", values);
12});
13promisesRace.catch(function(reason) {
14 console.timeEnd("Promise.race");
15 console.log("The fastest promise rejected with the following reason ", reason);
16});
همه promise-ها به صورت موازی اجرا میشوند. promise سوم در طی 2 ثانیه resolve میشود. به محض این که این کار صورت بگیرد promise بازگشتی از سوی promise.race به صورت resolve درمیآید.
حالت دوم
یکی از promise-ها اول reject میشود.
1console.time("Promise.race");
2var promisesArray = [];
3promisesArray.push(promiseTRSANSG(4));
4promisesArray.push(promiseTRSANSG(6));
5promisesArray.push(promiseTRSANSG(5));
6promisesArray.push(promiseTRJANSG(3));
7promisesArray.push(promiseTRSANSG(4));
8var promisesRace = Promise.race(promisesArray);
9promisesRace.then(function(values) {
10 console.timeEnd("Promise.race");
11 console.log("The fasted promise resolved", values);
12});
13promisesRace.catch(function(reason) {
14 console.timeEnd("Promise.race");
15 console.log("The fastest promise rejected with the following reason ", reason);
16});
همه promise-ها به صورت موازی اجرا میشوند. Promise چهارم در طی 3 ثانیه reject میشود. به محض این که این اتفاق میافتد، promise بازگشتی از سوی Promise.race به صورت reject درمیآید.
اینک که همه متدهای نمونه را نوشتیم، میتوانیم سناریوهای مختلف را تست کنیم و این تستها میتوانند در خود مرورگر اجرا شوند. به همین دلیل است که در مثالها هیچ فراخوانی API، پایگاه داده یا فایل را مشاهده نمیکنید. چون همه این موارد مثالهایی از زندگی واقعی هستند که برای راهاندازی و تست کردنشان به تلاش بیشتری نیاز داریم. با استفاده از تابع تأخیر میتوانیم سناریوهای مشابهی را بدون نیاز به این تنظیمات اضافی به دست بیاوریم. شما میتوانید به راحتی با مقادیر مختلف بازی کنید و بدین ترتیب سناریوهای متفاوت را مورد بررسی قرار دهید. شما میتوانید از ترکیبی از متدهای promiseTRJANSG، promiseTRSANSG و promiseTRRARNOSG برای شبیهسازی سناریوهای کافی جهت درک کامل promise –ها بهره بگیرید. ضمناً استفاده از متدهای console.time پیش و پس از بلوکهای مرتبط کد به شناسایی سادهتر این که promise-ها به صوت موازی یا ترتیبی اجرا میشوند کمک میکند. در قطعه کد زیر همه مثالهای فوق به صورت یکجا ارائه شده است:
1var keepsHisWord;
2keepsHisWord = true;
3promise1 = new Promise(function(resolve, reject) {
4 if (keepsHisWord) {
5 resolve("The man likes to keep his word");
6 } else {
7 reject("The man doesnt want to keep his word");
8 }
9});
10console.log(promise1);
11
12promise2 = new Promise(function(resolve, reject) {
13 setTimeout(function() {
14 resolve({
15 message: "The man likes to keep his word",
16 code: "aManKeepsHisWord"
17 });
18 }, 10 * 1000);
19});
20console.log(promise2);
21
22keepsHisWord = false;
23promise3 = new Promise(function(resolve, reject) {
24 if (keepsHisWord) {
25 resolve("The man likes to keep his word");
26 } else {
27 reject("The man doesn't want to keep his word");
28 }
29});
30console.log(promise3);
31
32var momsPromise = new Promise(function(resolve, reject) {
33 momsSavings = 20000;
34 priceOfPhone = 60000;
35 if (momsSavings > priceOfPhone) {
36 resolve({
37 brand: "iphone",
38 model: "6s"
39 });
40 } else {
41 reject("We donot have enough savings. Let us save some more money.");
42 }
43});
44momsPromise.then(function(value) {
45 console.log("Hurray I got this phone as a gift ", JSON.stringify(value));
46});
47momsPromise.catch(function(reason) {
48 console.log("Mom coudn't buy me the phone because ", reason);
49});
50momsPromise.finally(function() {
51 console.log(
52 "Irrespecitve of whether my mom can buy me a phone or not, I still love her"
53 );
54});
55
56momsPromise.then(
57 function(value) {
58 console.log("Hurray I got this phone as a gift ", JSON.stringify(value));
59 },
60 function(reason) {
61 console.log("Mom coudn't buy me the phone because ", reason);
62 }
63);
64
65function getRandomNumber(start = 1, end = 10) {
66 //works when both start,end are >=1 and end > start
67 return parseInt(Math.random() * end) % (end-start+1) + start;
68}
69
70function getRandomNumber(start = 1, end = 10) {
71 //works when both start and end are >=1
72 return (parseInt(Math.random() * end) % (end - start + 1)) + start;
73}
74var promiseTRRARNOSG = (promiseThatResolvesRandomlyAfterRandomNumnberOfSecondsGenerator = function() {
75 return new Promise(function(resolve, reject) {
76 let randomNumberOfSeconds = getRandomNumber(2, 10);
77 setTimeout(function() {
78 let randomiseResolving = getRandomNumber(1, 10);
79 if (randomiseResolving > 5) {
80 resolve({
81 randomNumberOfSeconds: randomNumberOfSeconds,
82 randomiseResolving: randomiseResolving
83 });
84 } else {
85 reject({
86 randomNumberOfSeconds: randomNumberOfSeconds,
87 randomiseResolving: randomiseResolving
88 });
89 }
90 }, randomNumberOfSeconds * 1000);
91 });
92});
93var testProimse = promiseTRRARNOSG();
94testProimse.then(function(value) {
95 console.log("Value when promise is resolved : ", value);
96});
97testProimse.catch(function(reason) {
98 console.log("Reason when promise is rejected : ", reason);
99});
100// Let us loop through and create ten different promises using the function to see some variation. Some will be resolved and some will be rejected.
101for (i=1; i<=10; i++) {
102 let promise = promiseTRRARNOSG();
103 promise.then(function(value) {
104 console.log("Value when promise is resolved : ", value);
105 });
106 promise.catch(function(reason) {
107 console.log("Reason when promise is rejected : ", reason);
108 });
109}
110
111var promise3 = Promise.reject("Not interested");
112promise3.then(function(value){
113 console.log("This will not run as it is a resolved promise. The resolved value is ", value);
114});
115promise3.catch(function(reason){
116 console.log("This run as it is a rejected promise. The reason is ", reason);
117});
118
119var promise4 = Promise.resolve(1);
120promise4.then(function(value){
121 console.log("This will run as it is a resovled promise. The resolved value is ", value);
122});
123promise4.catch(function(reason){
124 console.log("This will not run as it is a resolved promise", reason);
125});
126
127
128var promise4 = Promise.resolve(1);
129promise4.then(function(value){
130 console.log("This will run as it is a resovled promise. The resolved value is ", value);
131});
132promise4.then(function(value){
133 console.log("This will also run as multiple handlers can be added. Printing twice the resolved value which is ", value * 2);
134});
135promise4.catch(function(reason){
136 console.log("This will not run as it is a resolved promise", reason);
137});
138
139
140var promiseTRSANSG = (promiseThatResolvesAfterNSecondsGenerator = function(
141 n = 0
142) {
143 return new Promise(function(resolve, reject) {
144 setTimeout(function() {
145 resolve({
146 resolvedAfterNSeconds: n
147 });
148 }, n * 1000);
149 });
150});
151var promiseTRJANSG = (promiseThatRejectsAfterNSecondsGenerator = function(
152 n = 0
153) {
154 return new Promise(function(resolve, reject) {
155 setTimeout(function() {
156 reject({
157 rejectedAfterNSeconds: n
158 });
159 }, n * 1000);
160 });
161});
162
163console.time("Promise.All");
164var promisesArray = [];
165promisesArray.push(promiseTRSANSG(1));
166promisesArray.push(promiseTRSANSG(4));
167promisesArray.push(promiseTRSANSG(2));
168var handleAllPromises = Promise.all(promisesArray);
169handleAllPromises.then(function(values) {
170 console.timeEnd("Promise.All");
171 console.log("All the promises are resolved", values);
172});
173handleAllPromises.catch(function(reason) {
174 console.log("One of the promises failed with the following reason", reason);
175});
176
177console.time("Promise.All");
178var promisesArray = [];
179promisesArray.push(1);
180promisesArray.push(4);
181promisesArray.push(2);
182var handleAllPromises = Promise.all(promisesArray);
183handleAllPromises.then(function(values) {
184 console.timeEnd("Promise.All");
185 console.log("All the promises are resolved", values);
186});
187handleAllPromises.catch(function(reason) {
188 console.log("One of the promises failed with the following reason", reason);
189});
190
191
192console.time("Promise.All");
193var promisesArray = [];
194promisesArray.push(promiseTRSANSG(1));
195promisesArray.push(promiseTRSANSG(5));
196promisesArray.push(promiseTRSANSG(3));
197promisesArray.push(promiseTRJANSG(2));
198promisesArray.push(promiseTRSANSG(4));
199var handleAllPromises = Promise.all(promisesArray);
200handleAllPromises.then(function(values) {
201 console.timeEnd("Promise.All");
202 console.log("All the promises are resolved", values);
203});
204handleAllPromises.catch(function(reason) {
205 console.timeEnd("Promise.All");
206 console.log("One of the promises failed with the following reason ", reason);
207});
208
209
210console.time("Promise.race");
211var promisesArray = [];
212promisesArray.push(promiseTRSANSG(4));
213promisesArray.push(promiseTRSANSG(3));
214promisesArray.push(promiseTRSANSG(2));
215promisesArray.push(promiseTRJANSG(3));
216promisesArray.push(promiseTRSANSG(4));
217var promisesRace = Promise.race(promisesArray);
218promisesRace.then(function(values) {
219 console.timeEnd("Promise.race");
220 console.log("The fasted promise resolved", values);
221});
222promisesRace.catch(function(reason) {
223 console.timeEnd("Promise.race");
224 console.log("The fastest promise rejected with the following reason ", reason);
225});
226
227
228console.time("Promise.race");
229var promisesArray = [];
230promisesArray.push(promiseTRSANSG(4));
231promisesArray.push(promiseTRSANSG(6));
232promisesArray.push(promiseTRSANSG(5));
233promisesArray.push(promiseTRJANSG(3));
234promisesArray.push(promiseTRSANSG(4));
235var promisesRace = Promise.race(promisesArray);
236promisesRace.then(function(values) {
237 console.timeEnd("Promise.race");
238 console.log("The fasted promise resolved", values);
239});
240promisesRace.catch(function(reason) {
241 console.timeEnd("Promise.race");
242 console.log("The fastest promise rejected with the following reason ", reason);
243});
سخن پایانی
در این بخش برخی از قواعد مشخص برای استفاده از promise-ها را فهرستبندی کردهایم:
- هر زمان که از کدهای ناهمگام یا مسدودساز استفاده میکنید، از promise-ها بهره بگیرید.
- از نظر تمام مقاصد عملی resolve به then و reject به catch نگاشت میشود.
- مطمئن شوید که هر دو متد then. و catch. برای همه promise-ها نوشته شدهاند.
- اگر چیزی لازم باشد در هر دو حالت اجرا شود، باید در finally. نوشته شود.
- ما در زمان تغییر یافتن هر promise منفرد تنها یک نتیجه دریافت میکنیم.
- میتوان چندین دستگیره برای promise منفرد اضافه کرد.
- نوع بازگشتی همه متدها در شیء Promise چه متدهای استاتیک باشند یا متدهای پروتوتایپ، در هر حال Promise خواهد بود.
- در Promise.All ترتیب promise-ها در متغیر مقادیر صرفنظر از promise-ی که ابتدا resolve میشود، حفظ خواهد شد.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای JavaScript (جاوا اسکریپت)
- آموزش JavaScript ES6 (جاوا اسکریپت)
- مجموعه آموزشهای طراحی سایت
- جاوا اسکریپت چیست؟ — به زبان ساده
- آموزش جاوا اسکریپت — مجموعه مقالات جامع وبلاگ فرادرس
- بررسی اشیاء در جاوا اسکریپت
- حلقه for در جاوا اسکریپت — از صفر تا صد + مثال و کد
==
تو مثال مادر و کودک اشتباه نوشتی
هر جفتش یجوره !
اولی ریجکت بر میگردونه
حالا فرض کن اینهمه ادم میان اینو میخونن ،بیشتر گیج میشن
سلام و وقت بخیر دوست عزیز؛
یکی از تصاویر مطلب به طرز نادرستی درج شده بود که در پی تذکر شما اصلاح شد.
از این که خوانندگان تیزبینی همچون شما همراه مجله فرادرس هستند افتخار میکنیم.
با سپاس فروان.
خیلی عالی، ساده و قابل فهم.
ممنون