amirabbas ajoudani
amirabbas ajoudani
خواندن ۴ دقیقه·۳ سال پیش

پشت پرده promise ها چه می‌گذرد؟

توی این مطلب قصد داریم به promise ها از یک زاویه دیگه نگاه کنیم. اول از همه بگم که این مطلب برای افرادی نوشته شده که دانش خوبی از زبان  javascript دارن و علاقه دارن مبحث  promise ها رو عمیق تر یاد بگیرن.


حالت های promise

یک promise بعد از initial شدن در وضعیت pending قرار میگیره، اگه promise   با مقدار x resolve  بشه و ضعیت promise به fulfilled تغییر کرده و x، مقدار  fulfillment اون promise میشه و اگه  promise   با مقدار e reject بشه وضعیت promise به rejected تغییر میکنه و e مقدار rejection اون promise میشه (resolve یا reject شدن یک promise تنها یکبار انجام میشه).

به شکل زیر نگاه کنین:



بررسی مثال ساده


کد مثال ۱
کد مثال ۱


با اجرای این کد بعد از گذشت ۳ ثانیه promise  resolve شده و مقدار fulfillment value is :1 در خروجی چاپ میشه

بررسی ابتدایی تابع then

 این تابع دو callback function میگیره (onFulfilled,onRejected)، اگه promise resolve بشه تابع onFulfilled با مقداری که promise با اون resolve شده فراخوانی میشه، یا درصورت reject شدن promise، تابع onRejected با مقداری که promise با اون reject شده فراخوانی میشه.

برای اینکه دقیق تر متوجه عملکرد این تابع بشیم،قطعه کد زیر رو ببینید(در واقعیت پیاده سازی تابع به این شکل نیست اما بر اساس همین منطق عمل میکنه):


 شبه کد پیاده سازی تابع then (به نام ها دقت نکنید)
شبه کد پیاده سازی تابع then (به نام ها دقت نکنید)


در ابتدا دو task برای حالت fulfillment و rejection ساخته شده که عملکرد اونها واضحه(درصورت resolve یا reject شدنه promise، هر کدوم callback function مربوط به همون وضعیت رو با مقداری که promise با اون resolve یا reject شده فراخوانی می‌کنند)سپس وضعیت فعلی promise بررسی میشه که حالت های زیر ممکنه اتفاق بیوفته:

  • اگه موقع فراخوانی تابع، promise در حالت pending باشه: نیازه که taskهای fulfillment و rejection در محلی داخل promise ذخیره بشه.
  • اگه موقع فراخوانی تابع، promise در حالت fulfilled یا rejected باشه(قبلا resolve یا reject شده باشه): نیازه که task مربوط به همون وضعیت داخل microtasks queue ( اگه نمیدونین چی هست مقاله Jake Archibald رو بخونین) قرار بگیره.


داستان تابع catch چیه؟

تابع catch در واقع همون تابع then هست که onRejected callback function رو نداره.

دو فراخوانی زیر معادل هم هستن:

  • catch(onRejected)
  • then(null,onRejected)


بررسی توابع resolve و reject

تا اینجا فقط از resolve یا reject کردن promise  اسم بردیم.برای فهمیدن منطق عملکرد اونا قطعه کد زیر رو ببینین (در واقعیت پیاده سازی تابع به این شکل نیست اما بر اساس همین منطق عمل میکنه):


شبه کد پیاده سازی تابع resolve (به نام ها دقت نکنین)
شبه کد پیاده سازی تابع resolve (به نام ها دقت نکنین)


چون  promise تنها یکبار میتونه resolve یا reject بشه، پس ابتدا این بررسی انجام میشه.اگه promise در وضعیتی جز pending باشه، خود promise برگشت داده میشه( که مجدد بتونیم روی اون then بزنیم). در غیر این صورت وضعیت promise به fulfilled تغییر میکنه، سپس مقداری که promise  با آن resolve شده در محلی ذخیره میشه و در نهایت تمام task های fulfillmentو rejection که ذخیره شده، پاک میشه و task های fulfillment داخل microtasks queue منتقل میشه.

تابع reject هم از همین منطق پیروی میکنه.


بررسی مثال دیگه


کد مثال ۲
کد مثال ۲


با اجرای این کد بعد از گذشت ۳ ثانیه مقادیر fulfillment value is: 1 و fulfillment value is: 2 به ترتیب در خروجی چاپ میشه.اما تابع then چطور این حالت (زنجیره شدن) رو هندل میکنه؟


بررسی دقیق تر تابع then

تابع then(همینطور catch) خودش یک  promise برمیگردونه. به قطعه کد زیر دقت کنین(در واقعیت پیاده سازی تابع به این شکل نیست اما بر اساس همین منطق عمل میکنه)


شبه کد پیاده سازی تابع then (به نام ها دقت نکنید)
شبه کد پیاده سازی تابع then (به نام ها دقت نکنید)


  • اگه تابع onFulfilled وجود داشته باشه و promise resolve شده باشه: fulfillmentTask بعد از فراخوانی شدن promise ای که return کرده رو resolve میکنه با مقدار return شده از تابع onFulfilled.
  • اگه تابع onFulfilled وجود نداشته باشه و promise resolve شده باشه: fulfillmentTask بعد از فراخوانی شدن promise ای که return کرده رو resolve میکنه با مقداری که promise فعلی resolve شده.
  • اگه تابع onRejected وجود داشته باشه و promise reject شده باشه: rejectionTask بعد از فراخوانی شدن promise ای که return کرده رو resolve میکنه با مقدار return شده از تابع onRejected.
  • اگه تابع onRejected وجود نداشته باشه و promise reject شده باشه: rejectionTask بعد از فراخوانی شدن promise ای که return کرده رو reject میکنه با مقداری که promise فعلی reject شده.


بررسی مثال پیچیده تر


کد مثال ۳
کد مثال ۳


با اجرای این کد بعد از گذشت ۳ ثانیه مقدار fulfillment value is: 1 و بعد از ۳ ثانیه دیگر ( جمعا ۶ ثانیه) مقدار another fulfillment value is: 2 در خروجی چاپ میشه.


?سوال مهم?

گفتیم که تابع then (همینطور catch) خودش یک promise جدید برمیگردونه؛ پس کد مثال ۳ باید به صورت زیر باشه:


یعنی promise ای که ما return کردیم، باید wrap بشه درونه promise ای که خوده then (یا catch) return میکنه. اما در واقعیت کد مثال ۳ (عکس قبل از عکس بالا) درسته. پس داستان چیه??

Flattening Promises

اگه بخوام مختصر و مفید راجبش بگم، یه چیزی داریم به اسم thenable (هر چیزی که then داشته باشه ؛ مثل خود  promise که تابع then داره). زمانی که شما یک thenable (لزوما promise نیست و میتونه یه object باشه که تابع then داره) از تابع then (یا catch) return میکنین،دیگه داخل promise ای که then (یا catch) return میکنه، wrap نمیشه بلکه promise قفل میشه روی thenable که اگه resolve یا reject بشه، promise هم resolve یا reject میشه. شکل زیر حالت promise برگشت داده شده از تابع then (یا catch) رو نشون میده:



اگه علاقه مند بودین بیشتر راجب این مبحث بدونین لینک زیر کامل مطالب رو گفته:

Flattening Promises in the ECMAScript specification

در آخر هم باید بگم ممنون که زمان گذاشتین و این مطلب رو خوندین و خوشحال میشم اگه نظری داشتین برام comment کنین??

 promisejavascript
شاید از این پست‌ها خوشتان بیاید