آموزش Promise در جاوا اسکریپت

از promise مانند callback function برای مدیریت کردن دستورات غیر همزمان یا asynchronous استفاده می‌کنیم.

آموزش Promise در جاوا اسکریپت

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

تصور کنید که یک خواننده برتر هستید و طرفداران روز و شب آهنگ آینده شما را می خواهند.

برای ایجاد کمی آرامش، قول می دهید پس از انتشار آن را برای آنها ارسال کنید. شما یک لیست به طرفداران خود می دهید. آنها می توانند آدرس ایمیل خود را پر کنند، به طوری که وقتی آهنگ در دسترس قرار گرفت، همه کسانی که اشتراک دارند فورا آن را دریافت کنند. و حتی اگر مشکلی پیش بیاید، مثلاً آتش سوزی در استودیو، به طوری که نتوانید آهنگ را منتشر کنید، همچنان به آنها اطلاع داده شود.

همه خوشحال هستند: شما، چون مردم دیگر برای شما سرشلوغی ایجاد نمی کنند، و طرفداران، چون آهنگ را از دست نمی دهند.

این یک قیاس واقعی برای چیزهایی است که ما اغلب در برنامه نویسی داریم:

1- یک “producing code” که کاری انجام می دهد و زمان می برد. به عنوان مثال، کدهایی که داده ها را از طریق شبکه بارگیری می کند. این یک "خواننده" است.

2- یک “consuming code” که نتیجه “producing code” را پس از آماده شدن می خواهد. بسیاری از توابع ممکن است به آن نتیجه نیاز داشته باشند. اینها "طرفداران" هستند.

3- promise یک شی خاص جاوا اسکریپت است که “producing code” و “consuming code” را به هم پیوند می دهد. از نظر قیاس ما: این "فهرست اشتراک" است. “producing code” هر مقدار زمان برای تولید نتیجه promise نیاز داشته باشد می گیرد و "promise" آن نتیجه را در صورت آماده شدن برای همه کدهای مشترک در دسترس قرار می دهد.

این تشبیه خیلی دقیق نیست، زیرا promise های جاوا اسکریپت پیچیده تر از یک لیست اشتراک ساده است: آنها دارای ویژگی ها و محدودیت های اضافی هستند. اما برای شروع خوب است.

Promise in javascript

برای فهم بیشتر مثال دیگری را در نظر بگیرید. همانطور که می دانیم promise به معنای قول دادن و عهد و پیمان است. فرض كنيد به دوست خود قول داده ايد كه آخر هفته با او به سینما برويد. حال دو حالت وجود دارد. از آنجايی كه قرار است آخر هفته كه يك زمان در آينده است، اين كار انجام شود، احتمال دارد بتوانيد به قول خود پايبند باشيد، و احتمال دارد كاری برای شما پيش بيايد و نتوانيد به قول خود عمل كنيد. مفهوم promise در زبان برنامه نویسی جاوا اسکریپت رفتاری مشابه به اين دارد.

کاربرد و مزایای promise در زبان جاوا اسکریپت

در جاوا اسكريپت promise كاربردی مشابه callback function دارد. به این معنی که از promise همانند callback function برای مدیریت کردن دستورات غیر همزمان یا asynchronous استفاده می کنیم. اما منظور از دستورات asynchronous چیست؟

همانطور که می دانیم زبان جاوا اسکریپت یک زبان مفسری است. در زبان های مفسری، اجرای کدها به صورت خط به خط است. همچنین جاوا اسکریپت یک زبان رویدادگرا نیز هست. به این معنی که زمانیکه در ترجمه خط به خط کد ها مفسر به یک بخشی از کد برای مثال به یک تابع برخورد می کند که فرآیند دریافت پاسخ توسط تابع طولانی است، همزمان با اجرای آن بخش از کد، به خط های بعدی برنامه رفته و آن ها را اجرا می کند. زمانی که پاسخ از تابع زمانبر دریافت شد، برای اجرا باید منتظر اتمام اجرای کد های جلوتر خود باشد. به نوعی وارد یک صف اجرا می شود که برای اجرا باید منتظر شود که به اول این صف برسد و سپس اجرا شود. به دو صورت زیر انجام می شود:

Asynchronous یا پردازش ناهمگام (غیر همزمان): در این نوع پردازش، زمانی که پردازشگر شروع به پردازش می کند، بدون اینکه منتظر اتمام پردازش اول باشد، پردازش دوم را شروع می کند.

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

شرایطی را در نظر بگیرید، که در خواستی را به یک سرور ارسال کرده اید، تا اطلاعاتی را دریافت کنید، که در ادامه به آن اطلاعات احتیاج دارید، تا توابعی که تعریف کرده اید، از آن اطلاعات استفاده کنند. فرض کنید ارسال پاسخ از سمت سرور کمی طولانی شده است. می خواهیم کاری کنیم تازمانی که پاسخ سرور مشخص نشده است، کد های بعدی اجرا نشود. راه حل چیست؟ راه حل این است که از promise یا callback function استفاده کنیم. (در آموزش قبلی مفهوم callback در زبان برنامه نویسی جاوا اسکریپت به صورت کامل توضیح داده شده است).

از توابع callback استفاده کنیم یا promise ؟

همانطور که قبلاً گفته شد کاربرد promise همانند callback function است. حال کدام یک از این دو انتخاب بهتری است؟

قبل از اینکه promise معرفی شود برای انجام چند وظیفه ی غیر هم زمان از callback function استفاده ی زیادی می شد. استفاده از callback function ها مشکلاتی نیز به همراه داشت، که یکی از این مشکلات این بود که در برخی موارد منجر به تولید کدی می شد، که مدیریت کردن و خطایابی آن بسیار مشکل بود و گاهی غیر قابل کنترل بود. به این کد به اصطلاح callback hell می گویند. در نتیجه برای جلوگیری از وقوع این مسئله promise معرفی شد.

مزایای promise در جاوا اسکریپت

promise به ما کمک می کند، کدهای خوانا و تمیز تری بنویسیم، که قابلیت خطایابی و مدیریت کردن بالاتری نسبت به callback function ها دارد. به طور کلی فوایداستفاده از promise عبارتند از:

1- promise قابلیت خواندن کد را افزایش می دهد.

2- با promise عملیات asynchronous را بهتر می شود مدیریت کرد.

3- در promise می توانیم خطاهارا بهتر مدریت کنیم.

تعریف Promise در زبان جاوا اسکریپت

زمانی که یک promise را در زبان javascript تعریف می کنیم، با فرا رسیدن زمان آن یا آن promise حل می شود، یا به هر دلیلی رد می شود. برای مثال فرض کنیم درخواستی را به سمت وب ارسال کرده ایم و منتظر در یافت پاسخ هستیم. حال دو حالت وجود دارد، یا به آن درخواست پاسخ داده می شود، و نتیجه ی در خواست را می بینیم، یا به هر دلیلی در خواست رد می شود، و در نتیجه Error ارسالی از سمت وب را مشاهده می کنیم.

سینتکس سازنده برای یک شیء promise به صورت زیر است:

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

تابعی که به promise جدید منتقل می شود، اجرا کننده (executor) نامیده می شود. هنگامی که promise جدید ایجاد می شود، اجرا کننده به طور خودکار اجرا می شود. این شامل producing code است که در نهایت باید نتیجه را ایجاد کند. از نظر قیاس فوق: اجرا کننده «خواننده» است.

آرگومان های آن که حل (resolve) و رد (reject) می شوند، callback هایی هستند که توسط خود جاوا اسکریپت ارائه می شوند. کد ما فقط در داخل اجرا کننده است.

وقتی که executor نتیجه را به دست می آورد، چه زود باشد چه دیر، مهم نیست، باید یکی از این callback ها را فراخوانی کند:

1- حل یا resolve (مقدار) - اگر کار با موفقیت به پایان رسید، به همراه مقدار نتیجه.

2- رد یا reject (خطا) - اگر خطایی رخ داده باشد، خطا همان شی خطا است.

بنابراین به طور خلاصه: executor به طور خودکار اجرا می شود و تلاش می کند تا یک کار را انجام دهد. هنگامی که با تلاش تمام شد، در صورت موفقیت آمیز بودن آن، resolve را فراخوانی می کند یا در صورت وجود خطا، reject را فراخوانی می کند.

شیء promise ای که توسط سازنده new Promise برگردانده شده است دارای ویژگی های internal زیر است:

1- حالت (state) - در ابتدا حالت انتظار" pending"، سپس به تکمیل شده " fulfilled" در صورت فراخوانی resolve و رد شده " rejected" در هنگام فراخوانی Reject تغییر می کند.

2- نتیجه (result) - در ابتدا تعریف نشده است، سپس با فراخوانی resolve(value) به value یا هنگام فراخوانی Reject(Error) به error تغییر می کند.

بنابراین اجرا کننده در نهایت promise را به یکی از این حالت ها منتقل می کند:

promise

بعداً خواهیم دید که چگونه "طرفداران" می توانند با این تغییرات مشترک (subscribe) شوند.

در اینجا یک مثال از سازنده promise و یک تابع اجرا کننده ساده با “producing code” است که زمان می برد (از طریق setTimeout):

state of a promise

با اجرای کد بالا می توانیم دو نکته را ببینیم:

1- executor به طور خودکار و بلافاصله فراخوانی می شود (با new Promise).

2- executor دو آرگومان دریافت می کند: resolve و reject. این توابع توسط موتور جاوا اسکریپت از پیش تعریف شده اند، بنابراین ما نیازی به ایجاد آنها نداریم. فقط باید وقتی آماده شدیم یکی از آنها را فراخوانی کنیم.

پس از یک ثانیه «پردازش»، اجرا کننده، resolve("done") را برای ایجاد نتیجه فراخوانی می کند. این عمل حالت شیء promise را تغییر می دهد:

fulfilled promise

این نمونه ای از تکمیل موفقیت آمیز کار بود، یک “fulfilled promise”.

و حال مثالی از اجرا کننده ای که promise را با یک خطا رد می کند:

rejected promise

فراخوانی برای (...)reject شیء promise را به حالت "rejected" منتقل می کند:

promise in javascript

به طور خلاصه، executor باید یک کار را انجام دهد (معمولاً کاری که زمان می برد) و سپس برای تغییر حالت شیء promise مربوطه، Resolve یا Reject را فراخوانی کند.

به آن promise که resolve یا reject می شود، مستقر شده “settled” گفته می شود، برخلاف promise اولیه که در حالت “pending” بود.

فقط و فقط یک نتیجه یا یک خطا می تواند وجود داشته باشد

executor باید فقط یک resolve یا یک reject را فراخوانی کند. هر تغییر حالت نهایی است.

تمام درخواست های دیگر برای resolve و reject نادیده گرفته می شوند:

resolve or reject a promise

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

همچنین، resolve/reject فقط انتظار یک آرگومان (یا هیچ کدام) دارد و آرگومان های اضافی را نادیده می گیرد.

Reject با Error objects

در صورتی که مشکلی پیش بیاید، executor باید Reject را فراخوانی کند. این عمل را می توان با هر نوع آرگومانی انجام داد (دقیقاً مانند resolve). اما توصیه می شود از Error objects یا اشیایی که از Error به ارث می برند استفاده کنید. دلیل و منطق آن به زودی مشخص خواهد شد.

فراخوانی سریع resolve/reject

در عمل، یک executor معمولاً کاری را به صورت ناهمزمان انجام می دهد و پس از مدتی resolve/reject را فراخوانی می کند، اما لازم نیست. ما همچنین می توانیم فوراً resolve یا resolve را فراخوانی کنیم، مانند زیر:

فراخوانی سریع resolve/reject

به عنوان مثال، این ممکن است زمانی اتفاق بیفتد که ما شروع به انجام یک کار می کنیم، اما بعد می بینیم که همه چیز از قبل تکمیل شده و در حافظه پنهان ذخیره شده است.

بسیار عالی. ما بلافاصله یک resolved promise داریم.

state و result به صورت internal هستند.

ویژگی های state و result مربوط به شیء Promise به صورت internal هستند. ما نمی توانیم مستقیماً به آنها دسترسی داشته باشیم. به همین دلیل می توانیم از متد های then/.catch/.finally. استفاده کنیم که در ادامه توضیح داده می شوند.

مصرف کنندگان: then, catch, finally

یک شی promise به عنوان لینکی بین اجرا کننده ("producing code" یا "خواننده") و توابع مصرف کننده ("طرفداران") عمل می کند که نتیجه یا خطا را دریافت می کند. توابع مصرف کننده را می توان با استفاده از متد های then، .catch. و finally. ثبت (subscribed) کرد.

then

مهمترین و اساسی ترین متد then. می باشد. سینتکس آن عبارت است از:

then in promise

اولین آرگومان then. تابعی است که با حل شدن promise اجرا می شود و نتیجه را دریافت می کند.

آرگومان دوم then. تابعی است که با رد شدن promise اجرا می شود و خطا را دریافت می کند.

به عنوان مثال، در اینجا یک واکنش به یک promise که با موفقیت حل شده نشان داده شده است:

پرامیس در زبان جاوا اسکریپت

اولین تابع اجرا شد.

و در صورت rejection ، مورد دوم:

پرامیس در زبان جاوا اسکریپت

اگر ما فقط به تکمیل موفقیت آمیز علاقه مند هستیم، می توانیم تنها یک آرگومان تابع برای .then ارائه دهیم:

successful promise in javascript

catch

اگر فقط به خطاها علاقه مند هستیم، می توانیم از null به عنوان اولین آرگومان استفاده کنیم:

.then(null, errorHandlingFunction)

یا می توانیم از .catch(errorHandlingFunction) استفاده کنیم که دقیقاً مشابه یکدیگر هستند:

catch in promise in javascript

فراخوانی .catch(f) یک آنالوگ کامل از .then(null, f) است، این فقط یک مخفف است.

finally

درست مانند finally clause در یک try {...} catch {...} معمولی، finally در promise ها وجود دارد.

فراخوانی .finally(f) شبیه به .then(f,f) است به این معنا که f همیشه زمانی که promise مستقر می شود اجرا می شود: خواه resolve شود یا reject شود.

finally یک کنترل کننده خوب برای انجام cleanup است، به عنوان مثال. نشانگرهای بارگیری خود را متوقف می کنیم، زیرا مهم نیست که خروجی چه باشد، دیگر به آنها نیازی نیست. مانند آنچه که در زیر مشاهده می کنید:

finally in promise in javascript

با این حال، finally(f) دقیقاً نام مستعار then(f,f) نیست. چند تفاوت ظریف وجود دارد:

1- یک کنترل کننده finally هیچ آرگومانی ندارد. در finally ما نمی دانیم که آیا promise موفق است یا نه. همه چیز درست است، زیرا وظیفه ما معمولاً انجام مراحل نهایی سازی « general» است.

2- یک کنترل کننده finally نتایج و خطاها را به کنترل کننده بعدی منتقل می کند.

به عنوان مثال، در اینجا نتیجه از طریق finally به then منتقل می شود:

promise in javascript

و در اینجا یک خطایی در promise وجود دارد که از طریق finally به catch انتقال می یابد:

promise in javascript

این بسیار راحت است، زیراfinally به معنای پردازش یک نتیجهpromise نیست. بنابراین آن را منتقل می کند.

ما می توانیم کنترل کننده ها را به promise های مستقر شده متصل کنیم.

زمانی کهpromise در حالت pending است، کنترل کننده های.then/catch/finally منتظر آن می مانند. در غیر این صورت، اگر promise ای قبلاً مستقر شده باشد، آنها اجرا می شوند:

promise in javascript

توجه داشته باشید که این باعث می شود promise ها قدرتمندتر از سناریوی واقعی "لیست اشتراک" باشند. اگر خواننده قبلا آهنگ خود را منتشر کرده باشد و سپس شخصی در لیست اشتراک ثبت نام کند، احتمالاً آن آهنگ را دریافت نخواهد کرد. اشتراک در زندگی واقعی باید قبل از رویداد انجام شود.

promise ها انعطاف پذیرتر هستند. ما می توانیم هر زمان که بخواهیم کنترل کننده ها را اضافه کنیم: اگر نتیجه از قبل وجود داشته باشد، آنها فقط اجرا می شوند.

در مرحله بعد، بیایید نمونه های عملی بیشتری را ببینیم که چگونه promise ها می توانند به ما در نوشتن کد ناهمزمان (asynchronous code) کمک کنند.

مثال : loadScript

ما تابع loadScript را برای بارگذاری یک اسکریپت داریم که یک نوع مبتنی بر callback است:

آموزش پرامیس ها در زبان برنامه نویسی جاوا اسکریپت

بیایید آن را با استفاده از Promises بازنویسی کنیم.

تابع جدید loadScript نیازی به callback نخواهد داشت. درعوض، یک شیء Promise را ایجاد و برمی گرداند که پس از اتمام بارگیری حل می شود. کد بیرونی می تواند کنترل کننده ها (subscribing functions) را با استفاده از .then به آن اضافه کند.

آموزش پرامیس ها در زبان برنامه نویسی جاوا اسکریپت

ما می توانیم فوراً چند مزیت را نسبت به الگوی مبتنی بر callback مشاهده کنیم:

promise ها به ما این امکان را می دهند که کارها را به ترتیب طبیعی انجام دهیم. ابتدا loadScript(script) را اجرا می کنیم و .then می نویسیم که با نتیجه چه کار کنیم. همچنین ما می توانیم هر چند بار که بخواهیم ,then را در یک Promise فراخوانی کنیم. هر بار، یک "طرفدار" جدید، یک تابع اشتراک جدید، به "لیست اشتراک" اضافه می کنیم.

اما هنگام فراخوانی loadScript(script, callback) باید یک تابع callback در اختیار داشته باشیم. به عبارت دیگر، قبل از فراخوانی loadScript باید بدانیم با نتیجه چه کنیم. همچنین فقط میتواند یک callback وجود داشته باشد.

بنابراین promise ها به ما code flow بهتر و انعطاف پذیری بیشتری می دهند. اما مزیت های بیشتری وجود دارد.

در ادامه برای فهم بیشتر، چند تمرین به همراه راه حل نشان داده شده است.

Re-resolve a promise

خروجی کد زیر چیست؟

Re-resolve a promise

راه حل

خروجی عبارت است از: 1.

فراخوانی دوم برای resolve نادیده گرفته می شود، زیرا فقط اولین فراخوانی مربوط به reject/resolve در نظر گرفته می شود. فراخوانی های بیشتر نادیده گرفته می شوند.

تاخیر با promise

تابع built-in با نام setTimeout از callbacks استفاده می کند. یک جایگزین مبتنی بر promise ایجاد کنید.

تابعdelay(ms) باید یک promise را برگرداند. این promise باید بعد از ms میلی ثانیه حل شود، به طوری که ما بتوانیم .then را به آن اضافه کنیم، مانند این:

delay with promise

راه حل به صورت زیر است:

delay with promise

لطفاً توجه داشته باشید که در این کار resolve بدون آرگومان فراخوانی می شود. ما هیچ مقداری را از تاخیر بر نمی گردانیم، فقط از تاخیر اطمینان حاصل کنید.

Promise آموزش Promise در جاوا اسکریپت مزایا و کاربرد Promise پرامیس ها

مقالات این دسته بندی