Promise در جاوا اسکریپت و کاربردهای آن – به زبان ساده

۱۶۵۹ بازدید
آخرین به‌روزرسانی: ۸ شهریور ۱۴۰۲
زمان مطالعه: ۱۶ دقیقه
دانلود PDF مقاله
Promise در جاوا اسکریپت و کاربردهای آن – به زبان سادهPromise در جاوا اسکریپت و کاربردهای آن – به زبان ساده

برخی از برنامه‌نویسان رابطه عشق و نفرت توأمان با جاوا اسکریپت دارند. اما در هر صورت جاوا اسکریپت همواره جذاب است. برای کسانی که به طور عمده روی جاوا و PHP کار کرده باشند، جاوا اسکریپت ممکن است بسیار متفاوت به نظر بیاید. در این نوشته به بررسی موضوع Promise در جاوا اسکریپت می‌پردازیم که یکی از جذاب‌ترین موضوعات در این زبان برنامه‌نویسی محسوب می‌شود.

997696

به طور مکرر می‌شنویم که برخی افراد می‌گویند با بهره‌گیری از Promise می‌توانید از شر callback-ها در جاوا اسکریپت رها شوید. با این که این واقعیت ممکن است یکی از مزیت‌های Promise باشد، اما نکات بسیار بیشتری در خصوص Promise وجود دارد که باید بدانیم. در این مقاله تلاش کرده‌ایم برخی از این موارد را مورد بحث و بررسی قرار دهیم.

مقدمه

زمانی که برای نخستین بار می‌خواهید روی جاوا اسکریپت کار کنید، اوضاع کمی ترسناک به نظر می‌رسد. می‌شنوید که برخی افراد می‌گویند جاوا اسکریپت یک زبان برنامه‌نویسی «همگام» (synchronous) است، در حالی که برخی دیگر می‌گویند «ناهمگام» (asynchronous) است. همچنین با عبارت‌های کد مسدودکننده، کد غیر مسدودکننده، الگوی طراحی رویداد-محور، چرخه عمر رویداد، پشته تابع، صف رویداد، bubbling ،polyfill ،babel ،angular ،ReactJS ،vue JS، و بسیاری از ابزارها و کتابخانه‌های دیگر مواجه می‌شوید.

البته نباید بترسید، چون این تجربه صرفاً برای شما رخ نداده و همه افرادی که اولین بار با این زبان آشنا می‌شوند، همین تجربه را از سر گذرانده‌اند. این حالت به نام «خستگی جاوا اسکریپت» (JavaScript Fatigue) نامیده می‌شود. «کُری هاوس» این وضعیت را در توییت زیر به این صورت توصیف کرده است:

خستگی جاوا زمانی اتفاق می‌افتد که افراد از ابزارهایی که لازم ندارند، برای حل مشکلاتی که ندارند، استفاده می‌کنند.

Promise در جاوا اسکریپت

جاوا اسکریپت یک زبان برنامه‌نویسی همگام است؛ اما به لطف تابع‌های callback می‌توان آن را به شکل یک زبان برنامه‌نویسی ناهمگام نیز درآورد.

Promise به زبان خیلی ساده

Promise در لغت به معنی «قول» و «قرار» است و در جاوا اسکریپت نیز معنایی کاملاً مشابه این دارد. بنابراین ابتدا به این می‌پردازیم که قول در زندگی روزمره به چه معنا است. تعریف قول در دیکشنری به شرح زیر است:

قول: تضمین این که فردی کاری را انجام خواهد داد و یا اتفاق خاصی خواهد افتاد.

بنابراین زمانی که فردی به شما قولی می‌دهد، چه اتفاقی می‌افتد؟

  1. این قول به شما اطمینان می‌دهد که کاری انجام خواهد شد، چه فردی که قول داده آن کار را شخصاً انجام دهد و یا اجرای آن را به دیگران محول کند. در هر صورت یک اطمینان به شما داده می‌شود که می‌توانید بر مبنای آن برنامه‌ریزی کنید.
  2. یک قول می‌تواند حفظ شود یا شکسته شود.
  3. زمانی که یک قول حفظ می‌شود شما انتظار دارید که یک خروجی به دست آید. شما می‌توانید از این خروجی برای کارها یا طرح‌های بعدی خود استفاده کید.
  4. زمانی که یک قول می‌شکند، احتمالاً می‌خواهید بدانید که چرا فرد قول دهنده نتوانسته است به آن وفادار بماند. زمانی که دلیل این مسئله را دانستید و مطمئن شدید که قول شکسته است می‌توانید در ادامه برنامه‌ریزی کنید که برای مدیریت این وضعیت چه کاری باید انجام شود.
  5. در زمانی که قولی داده می‌شود، ما تنها یک اطمینان به دست می‌آوریم. ما نمی‌توانیم بی‌درنگ بر مبنای آن کاری را انجام دهیم. ما زمانی می‌توانیم موارد موردنیاز خود را فرمول‌بندی و یا اجرا کنیم که قول عمل شده باشد (و خروجی به دست آید) یا قول بشکند (و با دانستن دلیل آن برای وضعیت شکست برنامه‌ریزی کنیم).
  6. این احتمال وجود دارد که هیچ خبری از کسی که قول داده بود به دست نیاید. در چنین مواردی، احتمالاً برای خود یک آستانه زمانی تعیین می‌کنید. فرض کنید مثلاً می‌گویید اگر شخصی که قول داده است تا 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 ساده را به منظور درک بهتر خود ایجاد کنیم.

Promise در جاوا اسکریپت
هر promise یک حالت و یک مقدار دارد.

از آنجا که این promise هم اینک resolve شده است، ما قادر نخواهیم بود حالت ابتدایی promise را مورد بازبینی قرار دهیم. بنابراین اجازه بدهید یک promise جدید ایجاد کنیم که resolve کردن آن به مقداری زمان نیاز داشته باشد. ساده‌ترین روش برای این کار استفاده از تابع setTimeOut است.

کد فوق یک promise ایجاد می‌کند که به صورت نامشروطی پس از 10 ثانیه resolve می‌شود. بدین ترتیب می‌توانیم حالت promise را تا زمانی که هنوز resolve نشده است، مورد بررسی قرار دهیم.

Promise در جاوا اسکریپت
حالت promise تا زمانی که resolve یا reject شود.

زمانی که 10 ثانیه از آغاز promise سپری شود، resolve خواهد شد. PromiseStatus و همچنین PromiseValue بر همین اساس به‌روزرسانی می‌شوند. همان طور که می‌بینید ما تابع resolve را طوری به‌روزرسانی کرده‌ایم که یک شیء JSON به جای یک رشته ساده بازگشت دهد. این کار صرفاً به این منظور صورت گرفته است که نشان دهیم می‌توانیم در تابع resolve مقادیر دیگری را نیز بازگشت دهیم.

Promise در جاوا اسکریپت
یک promise که پس از 10 ثانیه resolve می‌شود و یک شیء JSON به عنوان مقدار بازگشتی ارسال می‌کند.

اینک نگاهی به promise-هایی می‌اندازیم که reject می‌شوند. بدین منظور کد promise فوق را کمی تغییر داده و به صورت زیر درمی‌آوریم:

از آنجا که ما یک rejection مدیریت نشده ایجاد کرده‌ایم، مرورگر کروم یک خطا نمایش می‌دهد. فعلاً می‌توانید این خطا را نادیده بگیرید. در ادامه آن را نیز بررسی خواهیم کرد.

Promise در جاوا اسکریپت
reject در promise

همان طور که می‌بینید 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 از پشتیبانی کمی برخوردار است و از این رو قبل از استفاده از آن باید مورد به مورد بررسی صورت بگیرد.

Promise در جاوا اسکریپت

تصور کنید یک کودک دبستانی از مادر خود یک گوشی تلفن همراه می‌خواهد. مادرش می‌گوید: «من آخر این ماه یک تلفن برای تو خواهم خرید.»

در ادامه کد این داستان را در جاوا اسکریپت در حالتی که قول مادر در انتهای ماه اجرا شود یا نشود مشاهده می‌کنید:

خروجی کد فوق به صورت زیر خواهد بود:

اگر مقدار momsSavings را به 200000 تغییر دهیم، در این صورت مادر می‌تواند هدیه پسرش را بخرد. در چنین حالتی خروجی به صورت زیر خواهد بود:

Promise در جاوا اسکریپت
مادر به قول خود عمل می‌کند.

حال خود را جای کسی می‌گذاریم که می‌خواهد از این کتابخانه استفاده کند. ما خروجی را تقلید می‌کنیم و بدین ترتیب می‌توانیم با شیوه استفاده از آن و سپس استفاده کارآمد از آن آشنا شویم.

از آنجا که then. می‌تواند به دستگیره‌های onFulfilled و onRejected متصل شود، به جای نوشتن then. و catch. مجزا، می‌توانیم هر دو را در then. بنویسیم و کد به صورت زیر درمی‌آید:

اما برای افزایش خوانایی کد، بهتر است آن‌ها را جدا از هم نگه داریم.

برای این که مطمئن شویم که می‌توانیم همه این نمونه‌ها را در مرورگر به طور کلی و یا در مرورگر کروم به طور خاص اجرا کنیم، باید اطمینان حاصل کنیم که وابستگی‌های خارجی در نمونه کدهای خود نداریم. برای دستیابی به درک بهتری از موضوعات بیشتر یک تابع ایجاد می‌کنیم که promise بازگشتی آن به صورت تصادفی resolve یا reject می‌شود و بدین ترتیب می‌توانیم سناریوهای مختلف را بیازماییم. برای درک بهتر مفهوم تابع‌های ناهمگام یک تأخیر تصادفی را نیز در تابع خود تعریف می‌کنیم. از آنجا که به اعداد تصادفی نیاز داریم ابتدا باید یک تابع تصادفی ایجاد کنیم که عددی بین x و y بازگشت می‌دهد:

فرض کنید تابعی می‌سازیم که یک promise به ما بازمی‌گرداند. فرض کنید تابع خود را promiseTRRARNOSG بنامیم که اختصاری برای عبارت زیر است:

promiseThatResolvesRandomlyAfterRandomNumnberOfSecondsGenerator

این تابع یک promise ایجاد می‌کند که پس از مدت زمانی تصادفی بین 2 تا 10 ثانیه به صورت تصادفی یک نتیجه resolve یا reject ایجاد می‌کند. برای تصادفی ساختن موفقیت یا شکست نتیجه نیز یک تابع تصادفی بین اعداد 1 تا 10 ایجاد می‌کنیم. اگر عدد تصادفی کمتر از 5 باشد، promise به صورت resolve و در غیر این صورت reject خواهد شد.

اینک صفحه مرورگر را رفرش کنید و کد را در کنسول اجرا کنید تا خروجی‌های مختلف سناریوهای resolve و reject را ملاحظه کنید. در ادامه بررسی می‌کنیم که چگونه می‌توان promise-های مختلف ایجاد کرد و خروجی‌های آن‌ها را بدون نیاز به این کارها بررسی نمود.

متدهای استاتیک

چهار متد استاتیک در شیء Promise وجود دارند. دو مورد نخست متدهای کمکی یا میانبر هستند. این موارد به ایجاد promise-های resolve یا reject به روشی آسان کمک می‌کنند.

بدین ترتیب یک promise به صورت reject ایجاد می‌شود.

یک promise به صورت resolve ایجاد می‌کند.

دقت کنید که یک pomise چند دستگیره دارد. بنابراین می‌توانید کد را به صورت زیر به‌روزرسانی کنید:

و خروجی مانند زیر خواهد بود:

Promise در جاوا اسکریپت

دو متد بعدی به پردازش یک مجموعه از promise-ها کمک می‌کنند. زمانی که با چندین promise سر و کار دارید، بهتر است که ابتدا یک آرایه از promise-ها ایجاد کنید و سپس اقدامات ضروری را روی مجموعه promise-ها اجرا نمایید. برای درک این متدها نمی‌توان از promiseTRRARNOSG استفاده کرد، زیرا بیش از حد تصادفی است. بهتر است برخی promise-های با قطعیت بیشتر داشته باشیم تا بتوانیم رفتار آن‌ها را بهتر درک کنیم. در ادامه دو تابع ایجاد می‌کنیم. یکی از آن‌ها پس از n ثانیه resolve می‌شود و دیگری پس از n ثانیه reject خواهد شد.

اینک این تابع‌های کمکی را برای درک بهتر Promise.All مورد استفاده قرار می‌دهیم.

Promise.All

بر اساس مستندات MDN تعریف Promise.All چنین است:

متد (Promise.All(iterable یک promise منفرد بازمی‌گرداند که وقتی همه promise-ها در آرگومان itrable به صورت resolve درآیند، یا زمانی که آرگومان iterable شامل هیچ promise نباشد، resolve می‌شود. این متد زمانی reject می‌شود که promise اول reject شود.

حالت اول

همه promise-ها به صورت resolve درآیند. این وضعیت سناریوی با بیشترین فراوانی است.

Promise در جاوا اسکریپت
همه promise-ها به صورت resolve در آمده‌اند.

دو مشاهده مهم وجود دارند که باید به طور کلی از خروجی به دست آوریم.

  • مشاهده اول: promise سوم که 2 ثانیه طول می‌کشد، پیش از promise سوم که 4 ثانیه طول می‌کشد، به پایان می‌رسد. اما همان طور که در خروجی می‌بینید، ترتیب promise-ها در مقادیر حفظ شده است.
  • مشاهده دوم: یک تایمر کنسول اضافه کرده‌ایم تا بدانیم که Promise.All چه قدر طول می‌کشد. اگر promise-ها به صورت ترتیبی اجرا شوند، این زمان مجموعاً باید 7=2+4+1 طول بکشد. اما از تایمر ما مشخص است که این فرایند تنها 4 ثانیه طول می‌کشد. این اثباتی است بر این نکته که promise-ها به صورت موازی اجرا شده‌اند.

حالت دوم

زمانی که هیچ promise-ی وجود ندارد. این حالت کمترین فراوانی را دارد.

Promise در جاوا اسکریپت

از آنجا که هیچ promise-ی در آرایه وجود ندارد، promise بازگشتی به صورت resolve خواهد بود.

حالت سوم

به همان دلیلی که promise اول ریجکت شده است، ریجکت می‌شود.

Promise در جاوا اسکریپت

Promise.race

بر اساس مستندات MDN، متد Promise.race به صورت زیر تعریف‌شده است:

متد (Promise.race(iterable یک promise بازگشت می‌دهد که به محض این که یکی از promise-ها در iterable به صورت resolve یا reject در آید، یک promise به صورت موفق یا شکست خورده بازگشت می‌دهد که مقدار یا نتیجه promise را در خود دارد.

حالت اول

یکی از promise-ها اول resolve می‌شود.

همه promise-ها به صورت موازی اجرا می‌شوند. promise سوم در طی 2 ثانیه resolve می‌شود. به محض این که این کار صورت بگیرد promise بازگشتی از سوی promise.race به صورت resolve درمی‌آید.

حالت دوم

یکی از promise-ها اول reject می‌شود.

همه promise-ها به صورت موازی اجرا می‌شوند. Promise چهارم در طی 3 ثانیه reject می‌شود. به محض این که این اتفاق می‌افتد، promise بازگشتی از سوی Promise.race به صورت reject درمی‌آید.

اینک که همه متدهای نمونه را نوشتیم، می‌توانیم سناریوهای مختلف را تست کنیم و این تست‌ها می‌توانند در خود مرورگر اجرا شوند. به همین دلیل است که در مثال‌ها هیچ فراخوانی API، پایگاه داده یا فایل را مشاهده نمی‌کنید. چون همه این موارد مثال‌هایی از زندگی واقعی هستند که برای راه‌اندازی و تست کردنشان به تلاش بیشتری نیاز داریم. با استفاده از تابع تأخیر می‌توانیم سناریوهای مشابهی را بدون نیاز به این تنظیمات اضافی به دست بیاوریم. شما می‌توانید به راحتی با مقادیر مختلف بازی کنید و بدین ترتیب سناریوهای متفاوت را مورد بررسی قرار دهید. شما می‌توانید از ترکیبی از متدهای promiseTRJANSG، promiseTRSANSG و promiseTRRARNOSG برای شبیه‌سازی سناریوهای کافی جهت درک کامل promise –ها بهره بگیرید. ضمناً استفاده از متدهای console.time پیش و پس از بلوک‌های مرتبط کد به شناسایی ساده‌تر این که promise-ها به صوت موازی یا ترتیبی اجرا می‌شوند کمک می‌کند. در قطعه کد زیر همه مثال‌های فوق به صورت یکجا ارائه شده است:

سخن پایانی

در این بخش برخی از قواعد مشخص برای استفاده از promise-ها را فهرست‌بندی کرده‌ایم:

  1. هر زمان که از کدهای ناهمگام یا مسدودساز استفاده می‌کنید، از promise-ها بهره بگیرید.
  2. از نظر تمام مقاصد عملی resolve به then و reject به catch نگاشت می‌شود.
  3. مطمئن شوید که هر دو متد then. و catch. برای همه promise-ها نوشته شده‌اند.
  4. اگر چیزی لازم باشد در هر دو حالت اجرا شود، باید در finally. نوشته شود.
  5. ما در زمان تغییر یافتن هر promise منفرد تنها یک نتیجه دریافت می‌کنیم.
  6. می‌توان چندین دستگیره برای promise منفرد اضافه کرد.
  7. نوع بازگشتی همه متدها در شیء Promise چه متدهای استاتیک باشند یا متدهای پروتوتایپ، در هر حال Promise خواهد بود.
  8. در Promise.All ترتیب promise-ها در متغیر مقادیر صرف‌نظر از promise-ی که ابتدا resolve می‌شود، حفظ خواهد شد.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۱۴ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
limitedio
دانلود PDF مقاله
۳ دیدگاه برای «Promise در جاوا اسکریپت و کاربردهای آن – به زبان ساده»

تو مثال مادر و کودک اشتباه نوشتی
هر جفتش یجوره !
اولی ریجکت بر میگردونه
حالا فرض کن اینهمه ادم میان اینو میخونن ،بیشتر گیج میشن

سلام و وقت بخیر دوست عزیز؛
یکی از تصاویر مطلب به طرز نادرستی درج شده بود که در پی تذکر شما اصلاح شد.
از این که خوانندگان تیزبینی همچون شما همراه مجله فرادرس هستند افتخار می‌کنیم.
با سپاس فروان.

خیلی عالی، ساده و قابل فهم.
ممنون

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *