⭐️🌐 🎀 جاوا اسکریپت بصری شده: وعده ها و همگام سازی/انتظار

⭐️🌐 🎀 جاوا اسکریپت بصری شده: وعده ها و همگام سازی/انتظار
اگر در سال 2024 (یا بعد از آن) اینجا هستید، در اینجا یک ویدیو به روز شده است:
آیا تا به حال مجبور شده اید با کدهای JS مقابله کنید که … آنطور که شما انتظار داشتید اجرا نمی شد؟ شاید به نظر می رسید که توابع در زمان های تصادفی و غیرقابل پیش بینی اجرا شده اند یا اجرا به تأخیر افتاده است. این احتمال وجود دارد که شما با یک ویژگی جدید و جالبی که ES6 معرفی کرده سر و کار داشته باشید: وعده ها!
کنجکاوی من از سال ها پیش نتیجه داد و شب های بی خوابی بار دیگر به من فرصت ساخت چند انیمیشن را داد. زمان صحبت در مورد وعده ها: چرا آیا از آنها استفاده می کنید، چگونه آیا آنها “زیر سرپوش” کار می کنند، و چگونه می توانیم آنها را بیشتر بنویسیم مدرن راه؟
اگر هنوز پست قبلی من در حلقه رویداد جاوا اسکریپت را نخوانده اید، ممکن است ابتدا آن را بخوانید. من دوباره حلقه رویداد را پوشش میدهم با فرض دانش اولیه درباره پشته تماس، Web API و صف، اما این بار برخی از ویژگیهای اضافی هیجانانگیز را نیز پوشش خواهیم داد.
[
✨♻️ JavaScript Visualized: Event Loop
Lydia Hallie ・ Nov 20 ’19
](/lydiahallie/javascript-visualized-event-loop-3dif)
اگر تا حدودی با وعده ها آشنا هستید، در اینجا چند میانبر برای صرفه جویی در زمان گرانبهای پیمایش وجود دارد.
| 🥳 مقدمه |
| ⚡️ نحو قول |
| ♻️ حلقه رویداد: وظایف ریز و وظایف (ماکرو) |
| 🚀 Async/Await |
مقدمه
هنگام نوشتن جاوا اسکریپت، اغلب باید با وظایفی سر و کار داشته باشیم که بر وظایف دیگر تکیه دارند! فرض کنید میخواهیم یک تصویر دریافت کنیم، آن را فشرده کنیم، یک فیلتر اعمال کنیم و آن را ذخیره کنیم.
اولین کاری که باید انجام دهیم، این است دریافت کنید تصویری که می خواهیم ویرایش کنیم. الف getImage
تابع می تواند از این مراقبت کند! تنها زمانی که آن تصویر با موفقیت بارگذاری شد، میتوانیم آن مقدار را به a ارسال کنیم resizeImage
تابع وقتی اندازه تصویر با موفقیت تغییر کرد، میخواهیم یک فیلتر روی تصویر موجود در آن اعمال کنیم applyFilter
تابع پس از فشردهسازی تصویر و افزودن فیلتر، میخواهیم تصویر را ذخیره کنیم و به کاربر اطلاع دهیم که همه چیز درست کار کرده است! 🥳
در پایان، به چیزی شبیه به این خواهیم رسید:
هوم… به چیزی در اینجا توجه کرده اید؟ اگرچه این … خوب، عالی نیست ما در نهایت با بسیاری از توابع پاسخ به تماس تو در تو مواجه می شویم که به تابع پاسخ به تماس قبلی وابسته هستند. این اغلب به عنوان یک نامیده می شود جهنم برگشت به تماس، همانطور که در نهایت با تعداد زیادی توابع پاسخ به تماس تو در تو مواجه می شویم که خواندن کد را بسیار دشوار می کند!
خوشبختانه، ما در حال حاضر چیزی به نام وعده می دهد برای کمک به ما! بیایید نگاهی بیندازیم که وعده ها چیست و چگونه می توانند در چنین شرایطی به ما کمک کنند! 😃
نحو قول
ES6 معرفی شد وعده ها. در بسیاری از آموزشها، چیزی شبیه به:
“وعده یک مکان نگهدار برای یک ارزش است که می تواند در زمانی در آینده حل شود یا رد شود.”
آره… این توضیح هیچ وقت همه چیز را برای من روشن نکرد. در واقع این فقط باعث شد که احساس کنم یک وعده یک جادوی عجیب، مبهم و غیرقابل پیش بینی است. پس بیایید ببینیم چه وعده هایی می دهد واقعا هستند.
ما می توانیم با استفاده از a یک وعده ایجاد کنیم Promise
سازنده ای که یک callback دریافت می کند. بسیار خوب، بیایید آن را امتحان کنیم!
صبر کن، چه چیزی برگشته؟
الف Promise
یک شی است که حاوی الف است وضعیت ، ([[PromiseStatus]]
) و الف ارزش ([[PromiseValue]]
). در مثال بالا می بینید که مقدار [[PromiseStatus]]
است "pending"
، و ارزش قول است undefined
.
نگران نباشید – هرگز مجبور نخواهید بود با این شی تعامل داشته باشید، حتی نمی توانید به آن دسترسی داشته باشید [[PromiseStatus]]
و [[PromiseValue]]
خواص! با این حال، ارزش این ویژگی ها هنگام کار با وعده ها مهم است.
ارزش از PromiseStatus
، دولت ، می تواند یکی از سه مقدار باشد:
- ✅
fulfilled
: قول بوده استresolved
. همه چیز خوب پیش رفت، هیچ خطایی در وعده داده نشد 🥳 - ❌
rejected
: قول بوده استrejected
. ارگ مشکلی پیش اومد.. - ⏳
pending
: عهد نه حل و نه رد شده (هنوز)، قول همچنان باقی استpending
.
خوب همه اینها عالی به نظر می رسد، اما چه زمانی وضعیت وعده است "pending"
، "fulfilled"
یا "rejected"
? و چرا این وضعیت حتی اهمیت دارد؟
در مثال بالا، ما فقط تابع callback ساده را پاس کردیم () => {}
به Promise
سازنده با این حال، این تابع callback در واقع دو آرگومان دریافت می کند. ارزش آرگومان اول که اغلب نامیده می شود resolve
یا res
، روشی است که زمانی که Promise باید فراخوانی می شود حل و فصل. مقدار آرگومان دوم که اغلب نامیده می شود reject
یا rej
، متد مقداری است که زمانی که Promise باید فراخوانی شود رد کردن ، مشکلی پیش آمد.
بیایید سعی کنیم و ببینیم که با فراخوانی هر یک از آنها ثبت می شود resolve
یا reject
روش! در مثال من، من به resolve
روش res
، و reject
روش rej
.
عالی! ما بالاخره می دانیم که چگونه از شر آن خلاص شویم "pending"
وضعیت و undefined
ارزش! این وضعیت یک وعده است "fulfilled"
اگر ما به resolve
روش، و مقام قول است "rejected
“اگر ما به آن استناد کنیم rejected
روش
این ارزش از یک وعده، ارزش [[PromiseValue]]
، مقداری است که به یا the منتقل می کنیم resolved
یا rejected
روش به عنوان استدلال آنها.
واقعیت جالب، من به جیک آرچیبالد اجازه دادم این مقاله را تصحیح کند و او در واقع اشاره کرد که یک اشکال در کروم وجود دارد که در حال حاضر وضعیت را به صورت
"resolved"
به جای"fulfilled"
. با تشکر از ماتیاس بیننس اکنون در قناری رفع شده است! 🥳🕺🏼
// تشخیص تم تیره var iframe = document.getElementById('tweet-1248179232775319559-992'); if (document.body.className.includes('dark-theme')) {iframe.src = “https://dev.to/hanzla-baig/https://platform.twitter.com/embed/Tweet.html?id=1248179232775319559&theme=dark“}
بسیار خوب، اکنون ما کمی بهتر می دانیم که چگونه این مبهم را کنترل کنیم Promise
شی اما برای چه استفاده می شود؟
در قسمت مقدماتی مثالی را نشان دادم که در آن یک تصویر می گیریم و فشرده می کنیم و یک فایلر اعمال می کنیم و ذخیره می کنیم! در نهایت، این به یک آشفتگی پاسخ به تماس تو در تو ختم شد.
خوشبختانه، Promises می تواند به ما در رفع این مشکل کمک کند! ابتدا، بیایید کل بلوک کد را بازنویسی کنیم، به طوری که هر تابع a را برگرداند Promise
در عوض
اگر تصویر بارگذاری شد و همه چیز خوب پیش رفت، اجازه دهید حل و فصل وعده با تصویر لود شده! در غیر این صورت، اگر در جایی هنگام بارگذاری فایل خطایی وجود داشت، اجازه دهید رد کردن وعده با خطایی که رخ داده است.
بیایید ببینیم وقتی این را در ترمینال اجرا می کنیم چه اتفاقی می افتد!
باحال یک وعده با مقدار داده های تجزیه شده بازگردانده شد، درست همانطور که انتظار داشتیم.
اما… حالا چی؟ ما به کل آن شی وعده اهمیت نمی دهیم، ما فقط به ارزش داده ها اهمیت می دهیم! خوشبختانه، روش های داخلی برای به دست آوردن ارزش یک وعده وجود دارد. به یک قول، می توانیم 3 روش را ضمیمه کنیم:
-
.then()
: بعد از یک قول تماس می گیرد حل شد. -
.catch()
: بعد از قول تماس می گیرد رد شد. -
.finally()
: همیشه تماس گرفته می شود، خواه وعده حل شود یا رد شده باشد.
این .then
متد مقدار ارسال شده به را دریافت می کند resolve
روش
این .catch
متد مقدار ارسال شده به را دریافت می کند rejected
روش
در نهایت، ما مقداری را داریم که بدون داشتن کل آن شیء وعده، با وعده حل شد! اکنون می توانیم با این ارزش هر کاری که بخواهیم انجام دهیم.
FYI، وقتی می دانید که یک قول همیشه حل می شود یا همیشه رد می شود، می توانید بنویسید Promise.resolve
یا Promise.reject
، با ارزشی که می خواهید قول را رد کنید یا حل کنید!
شما اغلب این نحو را در مثال های زیر می بینید 😄
در getImage
به عنوان مثال، ما در نهایت مجبور شدیم برای اجرای آنها چندین تماس را تودرتو کنیم. خوشبختانه، .then
گردانندگان می توانند در این زمینه به ما کمک کنند! 🥳
نتیجه از .then
خود یک ارزش قول است. این بدان معنی است که ما می توانیم به تعداد زیادی زنجیر کنیم .then
s همانطور که می خواهیم: نتیجه قبلی then
callback به عنوان آرگومان به بعدی ارسال می شود then
پاسخ به تماس
در مورد getImage
به عنوان مثال، ما می توانیم چندین زنجیر کنیم then
برای ارسال تصویر پردازش شده به تابع بعدی، تماس ها را پاسخ می دهد! بهجای پایان دادن به تماسهای تودرتوی زیاد، ما یک تماس تمیز دریافت میکنیم then
زنجیره ای
کامل! این نحو در حال حاضر بسیار بهتر از تماس های تو در تو به نظر می رسد.
وظایف خرد و (ماکرو) وظایف
بسیار خوب، بنابراین ما کمی بهتر می دانیم که چگونه یک وعده ایجاد کنیم و چگونه مقادیر را از یک وعده استخراج کنیم. بیایید کد بیشتری به اسکریپت اضافه کنیم و دوباره آن را اجرا کنیم:
صبر کن چی؟! 🤯
اول، Start!
وارد سیستم شد خوب، ما می توانستیم آن یکی را ببینیم که می آید: console.log('Start!')
در همان خط اول است! با این حال، دومین مقداری که ثبت شد، بود End!
، و نه ارزش قول حل شده! فقط بعد از End!
ثبت شد، ارزش قول ثبت شد. اینجا چه خبر است؟
ما بالاخره قدرت واقعی وعده ها را دیدیم! 🚀 اگرچه جاوا اسکریپت تک رشته ای است، اما می توانیم با استفاده از یک رفتار ناهمزمان اضافه کنیم. Promise
!
اما صبر کنید، آیا قبلاً آن را ندیده ایم؟ 🤔 آیا در حلقه رویداد جاوا اسکریپت، نمی توانیم از روش های بومی مرورگر نیز استفاده کنیم، مانند setTimeout
برای ایجاد نوعی رفتار ناهمزمان؟
بله! با این حال، در حلقه رویداد، در واقع دو نوع صف وجود دارد: (کلان) صف وظایف (یا فقط با صف وظیفه ) و صف microtask. صف وظایف (کلان) برای است (کلان) وظایف و صف microtask برای است وظایف خرد.
پس چی هست (کلان) وظیفه و چیست؟ ریز وظیفه? اگرچه تعداد کمی بیشتر از آنچه در اینجا توضیح خواهم داد وجود دارد، رایج ترین آنها در جدول زیر نشان داده شده است!
| وظیفه (کلان) | setTimeout
| setInterval
| setImmediate
|
| Microtask | process.nextTick
| Promise callback
| queueMicrotask
|
آه، ما می بینیم Promise
در لیست ریزکارها! 😃 وقتی یک Promise
حل می کند و آن را فرا می خواند then()
، catch()
یا finally()
، متد، پاسخ تماس درون متد به آن اضافه می شود صف microtask! این به این معنی است که تماس در داخل then()
، catch()
یا finally()
متد فوراً اجرا نمی شود، اساساً برخی رفتارهای ناهمگام را به کد جاوا اسکریپت ما اضافه می کند!
پس کی است الف then()
، catch()
یا finally()
پاسخ به تماس اجرا شد؟ حلقه رویداد اولویت متفاوتی به وظایف می دهد:
- تمام توابع موجود در آن در حال حاضر در پشته تماس اعدام شدن هنگامی که آنها یک مقدار را برگرداندند، از پشته خارج می شوند.
- وقتی پشته تماس خالی است، همه به صف شد وظایف خرد یکی یکی روی callstack قرار می گیرند و اجرا می شوند! (Microtasks خود میتواند ریزتسکهای جدید را نیز برگرداند، و به طور موثر یک حلقه microtask نامحدود ایجاد میکند.)
- اگر هم پشته تماس و هم صف ریز وظیفه خالی باشد، حلقه رویداد بررسی می کند که آیا وظایفی در صف وظایف (ماکرو) باقی مانده است یا خیر. وظایف بر روی پشته تماس ظاهر می شوند، اجرا می شوند و ظاهر می شوند!
بیایید به یک مثال سریع نگاهی بیندازیم، به سادگی با استفاده از:
-
Task1
: تابعی که فوراً به پشته تماس اضافه می شود، برای مثال با فراخوانی آنی در کد ما. -
Task2
،Task3
،Task4
: وظایف کوچک، به عنوان مثال یک وعدهthen
پاسخ به تماس یا وظیفه ای که به آن اضافه شده استqueueMicrotask
. -
Task5
،Task6
: یک کار (کلان)، برای مثال asetTimeout
یاsetImmediate
پاسخ به تماس
اول، Task1
مقداری را برگرداند و از پشته تماس خارج شد. سپس، موتور وظایفی را که در صف microtask قرار دارند بررسی کرد. هنگامی که همه وظایف در پشته تماس قرار گرفتند و در نهایت خاموش شدند، موتور وظایف را در صف وظایف (ماکرو) بررسی کرد، که روی پشته تماس ظاهر شد و زمانی که مقداری را برگرداندند، خاموش شد.
خوب، جعبه های صورتی کافی است. بیایید از آن با مقداری کد واقعی استفاده کنیم!
در این کد وظیفه ماکرو را داریم setTimeout
و وعده ریز وظیفه then()
پاسخ به تماس هنگامی که موتور به خط از setTimeout
تابع بیایید این کد را مرحله به مرحله اجرا کنیم و ببینیم چه چیزی ثبت می شود!
FYI سریع – در مثال های زیر روش هایی مانند این را نشان می دهم
console.log
،setTimeout
وPromise.resolve
در حال اضافه شدن به پشته تماس آنها روشهای داخلی هستند و در واقع در ردیابی پشته ظاهر نمیشوند – بنابراین اگر از دیباگر استفاده میکنید و آنها را در جایی نمیبینید نگران نباشید! این فقط توضیح این مفهوم را بدون اضافه کردن یک سری کد دیگ بخار آسان تر می کند
در خط اول، موتور با console.log()
روش به پشته تماس اضافه می شود و پس از آن مقدار را ثبت می کند Start!
به کنسول روش از پشته تماس خارج می شود و موتور ادامه می دهد.
موتور با setTimeout
روش، که به پشته تماس ظاهر می شود. این setTimeout
روش بومی مرورگر است: تابع تماس آن (() => console.log('In timeout')
) به Web API اضافه می شود، تا زمانی که تایمر تمام شود. اگرچه ما ارزش را ارائه کردیم 0
برای تایمر، تماس برگشت همچنان ابتدا به Web API فشار داده می شود و پس از آن به Web API اضافه می شود. (کلان) صف وظایف: setTimeout
یک کار کلان است!
موتور با Promise.resolve()
روش این Promise.resolve()
متد به پشته تماس اضافه می شود و پس از آن با مقدار حل می شود Promise!
. آن then
تابع callback به آن اضافه می شود صف microtask.
موتور با console.log()
روش بلافاصله به پشته تماس اضافه می شود و پس از آن مقدار را ثبت می کند End!
به کنسول، از پشته تماس خارج می شود و موتور به کار خود ادامه می دهد.
موتور می بیند که پشته تماس اکنون خالی است. از آنجایی که پشته تماس خالی است، باید بررسی کند که آیا وظایف در صف وجود دارد یا خیر صف microtask! و بله، قول وجود دارد then
پاسخ تماس منتظر نوبت خود است! روی پشته تماس ظاهر می شود، پس از آن مقدار حل شده قول را ثبت می کند: رشته Promise!
در این مورد
موتور می بیند که پشته تماس خالی است، بنابراین یک بار دیگر صف microtask را بررسی می کند تا ببیند آیا وظایف در صف قرار دارند یا خیر. نه، صف microtask همه خالی است.
وقت آن است که بررسی کنید (کلان) صف وظایف: setTimeout
پاسخ تماس هنوز در آنجا منتظر است! این setTimeout
تماس برگشتی در پشته تماس ظاهر می شود. تابع فراخوانی را برمی گرداند console.log
متد، که رشته را ثبت می کند "In timeout!"
. این setTimeout
پاسخ به تماس از پشته تماس خارج می شود.
بالاخره همه چیز تمام شد! 🥳 به نظر می رسد خروجی ای که قبلاً دیدیم خیلی غیرمنتظره نبود.
Async/Await
ES7 راه جدیدی را برای افزودن رفتار ناهمگام در جاوا اسکریپت و آسانتر کردن کار با وعدهها معرفی کرد! با معرفی async
و await
کلمات کلیدی، ما می توانیم ایجاد کنیم ناهمگام توابعی که به طور ضمنی یک وعده را برمی گرداند. اما .. چگونه می توانیم این کار را انجام دهیم؟ 😮
قبلاً دیدیم که میتوانیم با استفاده از Promise
شی، چه با تایپ باشد new Promise(() => {})
، Promise.resolve
، یا Promise.reject
.
به جای استفاده صریح از Promise
شی، اکنون می توانیم توابع ناهمزمان ایجاد کنیم که به طور ضمنی یک شی را برگردانید! این به این معنی است که دیگر مجبور نیستیم چیزی بنویسیم Promise
به خود اعتراض کنیم
اگرچه این واقعیت که ناهمگام توابع به طور ضمنی وعده بازگشت بسیار بزرگ است، قدرت واقعی است async
توابع را می توان در هنگام استفاده از await
کلمه کلیدی! با await
کلمه کلیدی، ما می توانیم تعلیق کند تابع ناهمزمان در حالی که منتظر آن هستیم await
ارزش ed یک وعده حل شده را برمی گرداند. اگر بخواهیم ارزش این وعده حل شده را به دست آوریم، همانطور که قبلاً با آن انجام دادیم then()
callback، می توانیم متغیرهایی را به آن اختصاص دهیم await
ed وعده ارزش!
بنابراین، ما می توانیم تعلیق کند یک تابع همگام؟ باشه عالیه ولی .. اصلا یعنی چی؟
بیایید ببینیم وقتی بلوک کد زیر را اجرا می کنیم چه اتفاقی می افتد:
هوم.. اینجا چه خبر است؟
ابتدا موتور با a مواجه می شود console.log
. بر روی پشته تماس ظاهر می شود، پس از آن Before function!
ثبت می شود
سپس، تابع async را فراخوانی می کنیم myFunc()
، پس از آن بدن تابع از myFunc
اجرا می شود. در اولین خط درون بدنه تابع، دیگری را فراخوانی می کنیم console.log
، این بار با رشته In function!
. این console.log
به پشته تماس اضافه میشود، مقدار آن را ثبت میکند و حذف میشود.
بدنه تابع همچنان اجرا می شود که ما را به خط دوم می رساند. در نهایت، یک را می بینیم await
کلمه کلیدی! 🎉
اولین چیزی که اتفاق می افتد این است که مقداری که انتظار می رود اجرا می شود: تابع one
در این مورد روی پشته تماس ظاهر می شود و در نهایت یک وعده حل شده را برمی گرداند. زمانی که قول حل شد و one
یک مقدار را برگرداند، موتور با آن مواجه می شود await
کلمه کلیدی
هنگام مواجهه با یک await
کلمه کلیدی، async
تابع می شود تعلیق شده است. ✋🏼 اجرای بدنه تابع مکث می شود ، و بقیه تابع async در یک اجرا می شود ریز وظیفه به جای یک کار معمولی!
اکنون که تابع async است myFunc
به حالت تعلیق در می آید که با آن مواجه شد await
کلمه کلیدی، موتور از تابع async خارج می شود و به اجرای کد در زمینه اجرایی که در آن تابع async فراخوانی شده است، ادامه می دهد: زمینه اجرای جهانی در این مورد! 🏃🏽♀️
در نهایت، هیچ کار دیگری برای اجرا در زمینه اجرای جهانی وجود ندارد! حلقه رویداد بررسی میکند که آیا ریزتسکهایی در صف وجود دارد یا خیر: و وجود دارد! همگام myFunc
تابع پس از حل مقدار از در صف قرار می گیرد one
. myFunc
به پشته تماس باز می گردد و از همان جایی که قبلاً متوقف شده بود به کار ادامه می دهد.
متغیر res
در نهایت ارزش خود را می گیرد، یعنی ارزش قول حل شده که one
بازگشت! استناد می کنیم console.log
با ارزش res
: رشته One!
در این مورد One!
به کنسول وارد می شود و از پشته تماس خارج می شود! 😊
بالاخره همه چیز تمام شد! دقت کردید چطور async
توابع در مقایسه با یک وعده متفاوت است then
? این await
کلمه کلیدی تعلیق می کند را async
تابع، در حالی که اگر ما استفاده می کردیم، بدن Promise همچنان اجرا می شد then
!
هوم این اطلاعات خیلی زیادی بود! 🤯 اصلاً نگران نباشید اگر هنوز هنگام کار با Promises کمی احساس ناراحتی می کنید، من شخصاً احساس می کنم که فقط به تجربه نیاز است تا متوجه الگوها شوید و هنگام کار با جاوا اسکریپت ناهمزمان احساس اطمینان کنید.
با این حال، امیدوارم رفتار “غیر منتظره” یا “غیر قابل پیش بینی” که ممکن است هنگام کار با جاوا اسکریپت غیر همگام با آن مواجه شوید، اکنون کمی منطقی تر باشد!
و مثل همیشه با خیال راحت با من تماس بگیرید! 😊
| ✨ توییتر | 👩🏽💻 اینستاگرام | 💻 GitHub | 💡 لینکدین | 📷 یوتیوب | 💌 ایمیل |
اگر می خواهید در مورد وعده ها بیشتر بدانید ایالت ها (و سرنوشت ها!)، این مخزن Github کار بسیار خوبی را در توضیح تفاوت ها انجام می دهد.
ES6 مطابق با جلسه TC39 سپتامبر 2013، مشخصات را وعده می دهد