آموزش جاوا اسکریپت پیشرفته : نحوه استفاده از promise
Promiseها پایه و اساس برنامهنویسی ناهمزمان در جاوااسکریپت مدرن هستند. Promise شیءای است که توسط یک تابع ناهمزمان برگردانده میشود و وضعیت فعلی عملیات را نشان میدهد. در زمانی که Promise به فراخواننده برگردانده میشود، عملیات اغلب تمام نشده است، اما شیء Promise روشهایی را برای مدیریت موفقیت یا شکست نهایی عملیات ارائه میدهد.
در مقاله قبلی، در مورد استفاده از callbackها برای پیادهسازی توابع ناهمزمان صحبت کردیم. با این طراحی، شما تابع ناهمزمان را فراخوانی میکنید و تابع callback خود را ارسال میکنید. تابع بلافاصله برمیگردد و پس از اتمام عملیات، callback شما را فراخوانی میکند.
با یک API مبتنی بر promise، تابع ناهمزمان عملیات را شروع میکند و یک شیء Promise را برمیگرداند. سپس میتوانید handlerها را به این شیء promise متصل کنید و این handlerها زمانی اجرا میشوند که عملیات موفقیتآمیز یا ناموفق باشد.
برای انجام این کار، یک درخواست HTTP به سرور ارسال میکنیم. در یک درخواست HTTP، ما یک پیام درخواست به یک سرور راه دور ارسال میکنیم و سرور پاسخی را برای ما ارسال میکند. در این حالت، ما یک درخواست برای دریافت یک فایل JSON از سرور ارسال میکنیم. به یاد دارید در مقاله قبلی، جایی که درخواستهای HTTP را با استفاده از API XMLHttpRequest ارسال کردیم؟ خب، در این مقاله، ما از API استفاده خواهیم کرد که جایگزین مدرن و مبتنی بر promise برای XMLHttpRequest است.
در اینجا ما این کار را انجام میدهیم:
فراخوانی API مربوط به و اختصاص مقدار بازگشتی به متغیر fetchPromise
بلافاصله پس از آن، ثبت متغیر fetchPromise. این باید چیزی شبیه به این خروجی دهد: ، که به ما میگوید ما یک شیء Promise داریم و حالتی دارد که مقدار آن "pending" است. حالت "pending" به این معنی است که عملیات واکشی هنوز در حال انجام است.
ارسال یک تابع handler به متد مربوط به Promise. وقتی (و اگر) عملیات واکشی موفقیتآمیز باشد، promise، handler ما را فراخوانی میکند و یک شیء Response را که شامل پاسخ سرور است، ارسال میکند.
ثبت پیامی مبنی بر اینکه درخواست را شروع کردهایم.
خروجی کامل باید چیزی شبیه به این باشد:
توجه داشته باشید که درخواست آغاز شده... قبل از دریافت پاسخ، ثبت میشود. برخلاف یک تابع همزمان، تابع در حالی که درخواست هنوز در حال انجام است، آن را برمیگرداند و برنامه ما را قادر میسازد تا پاسخگو باقی بماند. پاسخ، کد وضعیت ۲۰۰ (OK) را نشان میدهد، به این معنی که درخواست ما با موفقیت انجام شده است.
این احتمالاً بسیار شبیه به مثال مقاله قبلی است، جایی که ما کنترلکنندههای رویداد را به شیء XMLHttpRequest اضافه کردیم. به جای آن، ما یک کنترلکننده را به متد از promise بازگشتی ارسال میکنیم.
با استفاده از API مربوط به ، وقتی یک شیء Response دریافت میکنید، باید تابع دیگری را برای دریافت دادههای پاسخ فراخوانی کنید. در این حالت، ما میخواهیم دادههای پاسخ را به صورت JSON دریافت کنیم، بنابراین متد از شیء Response را فراخوانی میکنیم. مشخص میشود که نیز ناهمزمان است. بنابراین این موردی است که باید دو تابع ناهمزمان متوالی را فراخوانی کنیم.
در این مثال، مانند قبل، یک کنترلکنندهی به promise برگردانده شده توسط اضافه میکنیم. اما این بار، کنترلکنندهی ما را فراخوانی میکند و سپس یک کنترلکنندهی جدید را به promise برگردانده شده توسط ارسال میکند.
اما صبر کنید! مقالهی قبلی را به خاطر دارید، جایی که گفتیم با فراخوانی یک callback درون callback دیگر، به طور متوالی سطوح تو در تو بیشتری از کد خواهیم داشت؟ و گفتیم که این "جهنم callback" درک کد ما را دشوار کرده است؟ آیا این دقیقاً همان نیست، فقط با فراخوانیهای ؟
البته که همینطور است. اما ویژگی ظریف promiseها این است که then() خودش یک promise جدید برمیگرداند که با مقدار برگشتی تابع callback (در صورتی که تابع با موفقیت اجرا شود) برآورده میشود. این بدان معناست که میتوانیم (و مطمئناً باید) کد بالا را به این شکل بازنویسی کنیم:
به جای فراخوانی تابع دوم در داخل هندلر برای تابع ، میتوانیم promise برگردانده شده توسط را برگردانیم و تابع دوم را روی آن مقدار بازگشتی فراخوانی کنیم. این کار زنجیرهسازی promise نامیده میشود و به این معنی است که میتوانیم از افزایش مداوم سطح تورفتگی در هنگام نیاز به فراخوانی متوالی توابع غیرهمزمان جلوگیری کنیم.
قبل از اینکه به مرحله بعدی برویم، یک قطعه دیگر برای اضافه کردن وجود دارد. قبل از اینکه سعی کنیم درخواست را بخوانیم، باید بررسی کنیم که سرور آن را پذیرفته و قادر به مدیریت آن بوده است. ما این کار را با بررسی کد وضعیت در پاسخ و ارسال خطا در صورت "OK" نبودن آن انجام خواهیم داد:
این ما را به آخرین بخش میرساند: چگونه خطاها را مدیریت کنیم؟ API مربوط به
این نسخه از کد ما را امتحان کنید. ما یک مدیریتکننده خطا با استفاده از اضافه کردهایم و همچنین URL را طوری تغییر دادهایم که درخواست با شکست مواجه شود. میتواند به دلایل زیادی خطا ایجاد کند (برای مثال، به دلیل عدم اتصال به شبکه یا به دلیل نقص URL) و اگر سرور خطایی را برگرداند، خودمان خطا ایجاد میکنیم.
در مقاله قبلی، دیدیم که مدیریت خطا با callbackهای تو در تو میتواند بسیار دشوار شود و ما را مجبور میکند خطاها را در هر سطح تو در تو مدیریت کنیم.
برای پشتیبانی از مدیریت خطا، اشیاء Promise یک متد ارائه میدهند. این بسیار شبیه است: شما آن را فراخوانی میکنید و یک تابع handler را به آن ارسال میکنید. با این حال، در حالی که handler ارسال شده به زمانی فراخوانی میشود که عملیات ناهمزمان با موفقیت انجام شود، handler ارسال شده به زمانی فراخوانی میشود که عملیات ناهمزمان با شکست مواجه شود.
اگر را به انتهای یک زنجیره promise اضافه کنید، زمانی که هر یک از فراخوانیهای تابع ناهمزمان با شکست مواجه شود، فراخوانی میشود. بنابراین میتوانید یک عملیات را به صورت چندین فراخوانی متوالی تابع ناهمزمان پیادهسازی کنید و یک مکان واحد برای مدیریت همه خطاها داشته باشید.
این نسخه را اجرا کنید: باید خطایی که توسط کنترلکننده ما ثبت شده است را ببینید.
وعدهها با اصطلاحات کاملاً خاصی همراه هستند که ارزش توضیح دادن در مورد آنها را دارد.
اول، یک وعده میتواند در یکی از سه حالت زیر باشد:
- در حال انتظار pending : حالت اولیه. عملیات هنوز تکمیل نشده است (موفق یا ناموفق).
- انجام شده fulfilled : عملیات با موفقیت انجام شد. این زمانی است که کنترلکننده . وعده فراخوانی میشود.
- رد شده rejected : عملیات با شکست مواجه شد. این زمانی است که کنترلکننده وعده فراخوانی میشود.
توجه داشته باشید که معنای "موفق" یا "ناموفق" در اینجا به API مورد نظر بستگی دارد. به عنوان مثال، اگر (در میان دلایل دیگر) خطای شبکه مانع ارسال درخواست شود، وعده برگشتی را رد میکند، اما اگر سرور پاسخی ارسال کند، حتی اگر پاسخ خطایی مانند 404 یافت نشد باشد، وعده را اجرا میکند.
ما همچنین از چند اصطلاح دیگر برای توصیف وضعیت یک وعده استفاده میکنیم:
- کامل شده completed : وعده دیگر در حال انتظار نیست؛ یا انجام شده یا رد شده است.
- حلشده resolved : قول تکمیل شده است، یا برای دنبال کردن وضعیت قول دیگری «قفل» شده است. این یک مفهوم پیشرفتهتر است و زمانی مرتبط است که یک قول به قول دیگری وابسته باشد.
مقاله «بیایید درباره نحوه صحبت در مورد قولها صحبت کنیم» توضیح خوبی در مورد جزئیات این اصطلاح ارائه میدهد.
زنجیره وعده چیزی است که وقتی عملیات شما شامل چندین تابع ناهمزمان است و نیاز دارید که هر کدام قبل از شروع تابع بعدی تکمیل شوند، به آن نیاز دارید. اما روشهای دیگری نیز وجود دارد که ممکن است برای ترکیب فراخوانیهای تابع ناهمزمان نیاز داشته باشید و API Promise برخی از کمککنندهها را برای آنها فراهم میکند.
گاهی اوقات، شما نیاز دارید که همه وعدهها برآورده شوند، اما به یکدیگر وابسته نیستند. در چنین حالتی، شروع همه آنها با هم بسیار کارآمدتر است، سپس وقتی همه آنها برآورده شدند، مطلع شوید. متد چیزی است که شما در اینجا نیاز دارید. این متد آرایهای از وعدهها را میگیرد و یک وعده واحد را برمیگرداند.
وعده برگردانده شده توسط عبارت است از:
زمانی که و اگر همه وعدههای موجود در آرایه برآورده شوند، انجام میشود. در این حالت، کنترلکننده با آرایهای از تمام پاسخها، به همان ترتیبی که وعدهها به ارسال شدهاند، فراخوانی میشود.
زمانی که و اگر هر یک از وعدههای موجود در آرایه رد شوند، رد میشوند. در این حالت، کنترلکننده با خطایی که توسط وعدهای که رد شده است، ایجاد میشود، فراخوانی میشود.
به عنوان مثال:
در اینجا، ما سه درخواست به سه URL مختلف ارسال میکنیم. اگر همه آنها موفق شوند، وضعیت پاسخ هر یک را ثبت میکنیم. اگر هر یک از آنها شکست بخورند، آنگاه شکست را ثبت میکنیم.
با URLهایی که ارائه کردهایم، همه درخواستها باید انجام شوند، اگرچه برای درخواست دوم، سرور به جای 200 (OK)، 404 (یافت نشد) را برمیگرداند زیرا فایل درخواستی وجود ندارد. بنابراین خروجی باید به صورت زیر باشد:
اگر همین کد را با یک URL بد شکل، مانند این، امتحان کنیم:
سپس میتوانیم انتظار داشته باشیم که کنترلکننده اجرا شود و چیزی شبیه به این را ببینیم:
گاهی اوقات، ممکن است لازم باشد هر یک از مجموعهای از promiseها اجرا شوند، و مهم نیست کدام یک. در این صورت، شما را میخواهید. این مانند است، با این تفاوت که به محض اینکه هر یک از آرایه promiseها اجرا شوند، اجرا میشود، یا اگر همه آنها رد شوند، رد میشوند:
توجه داشته باشید که در این حالت نمیتوانیم پیشبینی کنیم که کدام درخواست واکشی ابتدا تکمیل خواهد شد.
اینها فقط دو مورد از توابع اضافی Promise برای ترکیب چندین Promise هستند. برای کسب اطلاعات بیشتر در مورد بقیه، به مستندات مرجع Promise مراجعه کنید.
کلمه کلیدی async روش سادهتری برای کار با کد مبتنی بر promise ناهمزمان به شما میدهد. اضافه کردن async در ابتدای یک تابع، آن را به یک تابع async تبدیل میکند:
درون یک تابع ناهمگام، میتوانید قبل از فراخوانی تابعی که یک promise را برمیگرداند، از کلمه کلیدی await استفاده کنید. این باعث میشود کد در آن نقطه منتظر بماند تا promise به نتیجه برسد، که در آن نقطه مقدار برآورده شده promise به عنوان مقدار بازگشتی در نظر گرفته میشود یا مقدار رد شده پرتاب میشود.
این به شما امکان میدهد کدی بنویسید که از توابع ناهمگام استفاده میکند اما شبیه کد همگام به نظر میرسد. به عنوان مثال، میتوانیم از آن برای بازنویسی مثال fetch خود استفاده کنیم:
در اینجا، ما تابع را فراخوانی میکنیم و به جای دریافت یک Promise، فراخوانیکننده ما یک شیء Response کاملاً کامل را برمیگرداند، درست مانند اینکه یک تابع همگام باشد!
ما حتی میتوانیم از یک بلوک برای مدیریت خطا استفاده کنیم، دقیقاً همانطور که اگر کد همگام بود، این کار را میکردیم.
البته توجه داشته باشید که توابع async همیشه یک promise را برمیگردانند، بنابراین نمیتوانید کاری مانند این انجام دهید:
در عوض، شما باید کاری مانند این انجام دهید:
در اینجا، ما را به کنترلکننده catch در promise برگشتی برگرداندیم. این یعنی کنترلکننده then ما مجبور نیست با حالتی که خطایی درون تابع fetchProducts رخ داده و باعث تعریفنشده بودن دادهها شده است، سر و کار داشته باشد. خطاها را به عنوان آخرین مرحله از زنجیره promise خود مدیریت کنید.
همچنین، توجه داشته باشید که فقط میتوانید از await درون یک تابع async استفاده کنید، مگر اینکه کد شما در یک ماژول جاوا اسکریپت باشد. این یعنی نمیتوانید این کار را در یک اسکریپت معمولی انجام دهید:
احتمالاً از توابع ناهمزمان زیاد استفاده خواهید کرد، در حالی که در غیر این صورت ممکن است از زنجیرههای وعده استفاده کنید، و آنها کار با وعدهها را بسیار شهودیتر میکنند.
به خاطر داشته باشید که درست مانند یک زنجیره وعده، await عملیات ناهمزمان را مجبور میکند که به صورت سری تکمیل شوند. این امر در صورتی ضروری است که نتیجه عملیات بعدی به نتیجه آخرین عملیات بستگی داشته باشد، اما اگر اینطور نباشد، چیزی مانند عملکرد بهتری خواهد داشت.