جاوااسکریپت - تفاوت بین میکروتسک و ماکروتسک در زمینه حلقه رویداد
من تازه خواندن مشخصات Promises/A+ را تمام کردهام و با اصطلاحات میکروتسک و ماکروتسک برخورد کردهام: ببینید http://promisesaplus.com/#notes
من قبلاً هرگز این اصطلاحها را نشنیدهام و حالا کنجکاوم که تفاوت آنها میتواند چه باشد؟
من قبلاً سعی کردهام اطلاعاتی در وب پیدا کنم، اما آنچه که پیدا کردهام فقط این پست از آرشیوهای w3.org است (که تفاوت را برای من توضیح نمیدهد): http://lists.w3.org/Archives/Public/public-nextweb/2013Jul/0018.html
علاوه بر این، من یک ماژول npm به نام "macrotask" پیدا کردهام: https://www.npmjs.org/package/macrotask دوباره، مشخص نیست که تفاوت دقیقاً چیست.
تمام چیزی که میدانم این است که به حلقه رویداد مربوط میشود، همانطور که در https://html.spec.whatwg.org/multipage/webappapis.html#task-queue و https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint توضیح داده شده است
میدانم که تئوراً باید بتوانم خودم تفاوتها را استخراج کنم، با توجه به این مشخصات WHATWG. اما مطمئنم که دیگران نیز میتوانند از یک توضیح کوتاه ارائه شده توسط یک متخصص بهرهمند شوند.
پاسخ شماره ۱
یک دور از حلقه رویداد دقیقا یک وظیفه از صف ماکروتسک پردازش خواهد شد (این صف به سادگی در مشخصات WHATWG به آن صف وظیفه گفته میشود). پس از اتمام این ماکروتسک، همه میکروتسکهای موجود پردازش خواهند شد، بهخصوص در همان چرخه دور. در حالی که این میکروتسکها پردازش میشوند، میتوانند میکروتسکهای بیشتری را صفبندی کنند، که همه به ترتیب اجرا خواهند شد، تا زمانی که صف میکروتسک خالی شود.
پیامدهای عملی این چیست؟
اگر یک میکروتسک بهصورت بازگشتی میکروتسکهای دیگری را صفبندی کند، ممکن است طول بکشد تا مکروتسک بعدی پردازش شود. این به این معناست که ممکن است با یک رابط کاربری مسدود شده مواجه شوید یا برخی از عملیات ورودی/خروجی خاتمه یافته در برنامه شما بدون استفاده باقی بمانند.
با این حال، حداقل در مورد تابع process.nextTick در Node.js (که ریزوظایف را صف میکند)، یک محافظت داخلی در برابر چنین مسدودسازی وجود دارد از طریق process.maxTickDepth. این مقدار به طور پیشفرض روی ۱۰۰۰ تنظیم شده است و پردازش بیشتر ریزوظایف پس از رسیدن به این حد را کاهش میدهد که اجازه میدهد ماکروز وظیفه بعدی پردازش شود)
پس کی از کدام استفاده کنیم؟
به طور کلی، از ریزکارها زمانی استفاده کنید که نیاز دارید کارها را به صورت ناهمگام به روش همگام انجام دهید (یعنی زمانی که میگویید این کار (ریز-) را در نزدیکترین زمان ممکن انجام دهید). در غیر این صورت، به درشتکارها پایبند باشید.
نمونهها
ماکروتسکها: setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, رندرینگ رابط کاربری
میکروتسکها: process.nextTick, Promises, queueMicrotask, MutationObserver
پاسخ شماره ۲
مفاهیم پایه در مشخصات:
- یک حلقه رویداد یک یا چند صف کار دارد. (صف کار، صف ماکروتسک است)
- هر حلقه رویداد یک صف میکروتسک دارد.
- صف وظیفه=صف ماکروتسک != صف میکروتسک
- یک کار ممکن است به صف ماکروتسک یا صف میکروتسک فرستاده شود
- وقتی یک کار به یک صف (ریز/کلان) اضافه میشود، منظور این است که آمادهسازی کار به پایان رسیده است، بنابراین کار میتواند اکنون اجرا شود.
و مدل فرآیند حلقه رویداد به شرح زیر است:
وقتی پشته تماس خالی است، مراحل را انجام دهید-
- قدیمیترین وظیفه (وظیفه A) را در صفهای وظیفه انتخاب کنید
- اگر کار A تهی باشد (یعنی صفهای کار خالی هستند)، به مرحله ۶ بروید
- تنظیم «وظیفه در حال اجرا» به «وظیفه A»
- اجرای «وظیفه A» (به معنی اجرای تابع بازخوانی)
- مقدار «وظیفه در حال اجرا» را به تهی تنظیم کنید، «وظیفه A» را حذف کنید
اجرای صف میکروتسک
- (الف) قدیمیترین تسک (تسک x) در صف میکروتسک را انتخاب کنید
- (ب). اگر وظیفه x تهی باشد (یعنی صفهای میکرووظیفه خالی هستند)، به مرحله (گ) بروید
- (c). تنظیم «وظیفه در حال اجرا» به «وظیفه x»
- (d).run "task x"
- (e). مقدار «وظیفه در حال اجرا» را به null قرار دهید، «وظیفه x» را حذف کنید
- (f).انتخاب قدیمیترین کار بعدی در صف میکروتسک، رفتن به مرحله (b)
- (g).اتمام صف میکروتسک؛
- به مرحله ۱ برو.
یک مدل فرآیند سادهشده به شرح زیر است:
- قدیمیترین وظیفه در صف ماکروتسک را اجرا کرده، سپس آن را حذف کنید.
- تمام وظایف موجود در صف میکروتسک را اجرا کنید، سپس آنها را حذف کنید.
- دور بعد: اجرای کار بعدی در صف ماکروتسک (پرش به مرحله ۲)
چیزی برای به خاطر سپردن:
وقتی یک تسک (در صف ماکروتسک) در حال اجرا است، ممکن است رویدادهای جدیدی ثبت شوند. بنابراین تسکهای جدیدی ممکن است ایجاد شوند. در زیر دو تسک تازه ایجاد شده آمده است:
callback تابع promiseA.then() یک تسک است
- promiseA حل شده/رد شده است: وظیفه در صف میکروتسک در نوبت فعلی حلقه رویداد قرار خواهد گرفت.
- callback تابع promiseA.then() یک تسک است
- promiseA حل شده/رد شده است: تسک در صف میکروتسک در دور فعلی حلقه رویداد قرار داده میشود.
- promiseA در حالت pending است: کار در دور بعدی حلقه رویداد به صف میکروتسکها ارسال خواهد شد (ممکن است در دور بعدی باشد)
- callback تابعی است که به setTimeout(callback, n) داده میشود و یک کار (task) است و حتی اگر n برابر با ۰ باشد، به صف macrotask اضافه خواهد شد؛
- وظیفه در صف میکروتسک در دور فعلی اجرا خواهد شد، در حالی که وظیفه در صف ماکروتسک باید منتظر دور بعدی حلقه رویداد بماند.
- همه ما میدانیم که 'کلیک'، 'اسکرول'، 'آژاکس'، 'setTimeout' کالبکها... تسک هستند، اما باید به یاد داشته باشیم که کد جاوااسکریپت به طور کلی در تگ اسکریپت نیز یک تسک است (یک ماکروتسک).
پاسخ شماره ۳
من فکر میکنم نمیتوانیم حلقه رویداد را جدا از پشته بررسی کنیم، بنابراین:
جاوااسکریپت سه «استک» دارد:
- پشته استاندارد برای تمام تماسهای همزمان (یک تابع تابع دیگری را فرا میخواند و غیره)
- صف میکروتسک (یا صف شغلی یا پشته میکروتسک) برای تمام عملیاتهای ناهمزمان با اولویت بالاتر (process.nextTick, Promises, Object.observe, MutationObserver)
- صف ماکروتسک (یا صف رویداد، صف تسک، صف ماکروتسک) برای تمام عملیاتهای غیرهمزمان با اولویت پایین (setTimeout، setInterval، setImmediate، requestAnimationFrame، I/O، رندر رابط کاربری)
|=======|
| macro |
| [...] |
| |
|=======|
| micro |
| [...] |
| |
|=======|
| stack |
| [...] |
| |
|=======|
و حلقه رویداد به این صورت کار میکند:
- همه چیز را از پایین به بالا از پشته اجرا کنید، و تنها وقتی که پشته خالی است، بررسی کنید که در صفهای بالا چه اتفاقی میافتد
- میکرو استک را بررسی کنید و همه چیز را آنجا اجرا کنید (در صورت نیاز) با کمک استک، یک میکروتسک پس از دیگری تا زمانی که صف میکروتسک خالی شود یا نیازی به اجرای هیچ چیزی نباشد و فقط در آن زمان است که استک ماکرو را بررسی کنید
- پشته ماکرو را بررسی کرده و همه چیز را در آن اجرا کنید (در صورت نیاز) با کمک پشته
استک میکرو اگر استک خالی نباشد دست نخورده باقی میماند. استک ماکرو اگر استک میکرو خالی نباشد یا نیازی به اجرا نداشته باشد، دست نخورده باقی میماند.
خلاصه: صف میکروتسک تقریباً همانند صف ماکروتسک است اما آن تسکها (process.nextTick, Promises, Object.observe, MutationObserver) نسبت به ماکروتسکها اولویت بالاتری دارند.
ریز مثل کلان است اما با اولویت بالاتر.
در اینجا شما کد «نهایی» برای درک همه چیز دارید.
console.log('stack [1]');
setTimeout(() => console.log("macro [2]"), 0);
setTimeout(() => console.log("macro [3]"), 1);
const p = Promise.resolve();
for(let i = 0; i < 3; i++) p.then(() => {
setTimeout(() => {
console.log('stack [4]')
setTimeout(() => console.log("macro [5]"), 0);
p.then(() => console.log('micro [6]'));
}, 0);
console.log("stack [7]");
});
console.log("macro [8]");
پاسخ شماره ۴
وظایف ماکرو شامل رویدادهای صفحهکلید، رویدادهای موس، رویدادهای تایمر (setTimeout)، رویدادهای شبکه، تجزیه HTML، تغییر URL و غیره است. یک وظیفه ماکرو نمایانگر کاری مستقل و جداگانه است. صف وظایف میکرو اولویت بالاتری دارد، بنابراین وظیفه ماکرو منتظر میماند تا همه وظایف میکرو اجرا شوند.
ریزکارها، کارهای کوچکتری هستند که وضعیت برنامه را بهروزرسانی میکنند و باید قبل از اینکه مرورگر به وظایف دیگر مانند بازرندر کردن رابط کاربری ادامه دهد، اجرا شوند. ریزکارها شامل بازگشتهای وعده (promise callbacks) و تغییرات تغییر DOM هستند. ریزکارها به ما امکان میدهند برخی اقدامات را قبل از بازرندر شدن رابط کاربری انجام دهیم، و بدین ترتیب از بازرندر غیرضروری رابط کاربری که ممکن است وضعیت ناسازگار برنامه را نشان دهد، جلوگیری میکنند.
جدا کردن کارهای ماکرو و میکرو این امکان را به حلقه رویداد میدهد تا انواع کارها را اولویتبندی کند؛ برای مثال، اولویت دادن به کارهایی که حساس به عملکرد هستند.
در یک تکرار حلقه، حداکثر یک کار ماکرو پردازش میشود (سایرین در صف منتظر باقی میمانند)، در حالی که تمام میکروتسکها پردازش میشوند.
هر دو صف وظیفه خارج از حلقه رویداد قرار دارند، تا نشان دهند که عمل افزودن وظایف به صفهای متناسبشان خارج از حلقه رویداد انجام میشود. در غیر این صورت، هر رویدادی که در حالی که کد جاوااسکریپت در حال اجرا است رخ دهد، نادیده گرفته میشود. عملهای کشف و افزودن وظایف به صورت جداگانه از حلقه رویداد انجام میشوند.
هر دو نوع وظیفه یکی یکی اجرا میشوند. وقتی یک وظیفه شروع به اجرا میکند، تا پایان کامل آن اجرا میشود. تنها مرورگر میتواند اجرای یک وظیفه را متوقف کند؛ برای مثال، اگر وظیفه زمان یا حافظه زیادی مصرف کند.
تمام میکروتسکها باید قبل از رندر بعدی اجرا شوند زیرا هدف آنها بهروزرسانی وضعیت برنامه قبل از وقوع رندر است.
مرورگر معمولاً سعی میکند صفحه را ۶۰ بار در ثانیه رندر کند. پذیرفته شده است که ۶۰ فریم در ثانیه نرخ یکنواخت بودن انیمیشنها را مشخص میکند. اگر بخواهیم برنامههایی با اجرای روان داشته باشیم، یک وظیفه واحد و تمام میکروتسکهای تولید شده توسط آن وظیفه بهطور ایدهآل باید در عرض ۱۶ میلیثانیه تکمیل شوند. اگر اجرای یک وظیفه بیش از چند ثانیه طول بکشد، مرورگر پیام «اسکریپت پاسخگو نیست» را نمایش میدهد.
مرجع جان رزگ - رازهای نینجای JS
