ایجاد وبسایتهایی که به سرعت به ورودیهای کاربر پاسخ میدهند، یکی از چالشبرانگیزترین جنبههای عملکرد وب بوده است - موردی که تیم Chrome برای کمک به توسعهدهندگان وب سخت کار کرده است. همین امسال، اعلام شد که معیار Interaction to Next Paint (INP) از وضعیت آزمایشی به وضعیت معلق تبدیل می شود. اکنون در مارس 2024 به عنوان Core Web Vital جایگزین First Input Delay (FID) می شود.
در تلاش مستمر برای ارائه API های جدید که به توسعه دهندگان وب کمک می کند تا وب سایت های خود را تا آنجا که می توانند سریع تر کنند، تیم Chrome در حال حاضر در حال اجرای یک آزمایش اولیه برای scheduler.yield
است که از نسخه 115 Chrome شروع می شود. scheduler.yield
یک افزودنی جدید پیشنهادی به API زمانبندی است که به روشی آسانتر و بهتر برای بازگرداندن کنترل به رشته اصلی نسبت به روشهایی که به طور سنتی بر آنها تکیه می شد، اجازه می دهد.
در بازده
جاوا اسکریپت از مدل run-to-completion برای مقابله با وظایف استفاده می کند. این بدان معنی است که وقتی یک کار روی رشته اصلی اجرا می شود، آن کار تا زمانی که برای تکمیل لازم باشد اجرا می شود. پس از تکمیل یک کار، کنترل به رشته اصلی باز می گردد ، که به رشته اصلی اجازه می دهد تا وظیفه بعدی را در صف پردازش کند.
جدای از موارد شدید که یک کار هرگز تمام نمی شود - مانند یک حلقه بی نهایت، برای مثال - تسلیم جنبه اجتناب ناپذیر منطق زمان بندی کار جاوا اسکریپت است. این اتفاق خواهد افتاد، فقط مسئله زمان است، و هر چه زودتر بهتر از دیرتر است. زمانی که انجام وظایف بیش از حد طول می کشد - به طور دقیق بیش از 50 میلی ثانیه - آنها به عنوان کارهای طولانی در نظر گرفته می شوند.
وظایف طولانی منبع پاسخگویی ضعیف صفحه هستند، زیرا توانایی مرورگر را برای پاسخگویی به ورودی کاربر به تاخیر می اندازند. هر چه کارهای طولانیتر اتفاق بیفتند - و هر چه طولانیتر اجرا شوند - احتمال اینکه کاربران این تصور را داشته باشند که صفحه تنبل است یا حتی احساس کنند که کاملاً خراب است بیشتر است.
با این حال، فقط به این دلیل که کد شما یک کار را در مرورگر شروع می کند به این معنی نیست که باید منتظر بمانید تا آن کار به پایان برسد تا کنترل به رشته اصلی بازگردد. میتوانید با تسلیم صریح در یک کار، پاسخگویی به ورودی کاربر در یک صفحه را بهبود بخشید، که این کار را در فرصت بعدی تکمیل میکند. این باعث میشود تا کارهای دیگر زودتر از زمانی که باید منتظر اتمام کارهای طولانی بمانند، در موضوع اصلی وقت بگیرند.
وقتی صراحتاً تسلیم میشوید، به مرورگر میگویید «هی، من میدانم که کاری که میخواهم انجام دهم ممکن است مدتی طول بکشد، و نمیخواهم قبل از پاسخ دادن به ورودی کاربر مجبور به انجام همه آن کارها باشید. یا کارهای دیگری که ممکن است مهم باشند." این ابزار ارزشمندی در جعبه ابزار توسعهدهندگان است که میتواند تا حد زیادی به سمت بهبود تجربه کاربر کمک کند.
مشکل با استراتژی های بازده فعلی
یک روش متداول برای بازده از setTimeout
با مقدار وقفه 0
استفاده می کند . این کار به این دلیل کار میکند که فراخوان ارسال شده به setTimeout
کار باقیمانده را به یک کار جداگانه منتقل میکند که برای اجرای بعدی در صف قرار میگیرد. به جای اینکه منتظر بمانید تا مرورگر به خودی خود عمل کند، می گویید "بیایید این بخش بزرگ کار را به قطعات کوچکتر تقسیم کنیم".
با این حال، تسلیم شدن با setTimeout
یک عارضه جانبی بالقوه نامطلوب دارد: کاری که بعد از نقطه تسلیم می آید به پشت صف کار می رود. وظایف برنامهریزیشده توسط تعاملات کاربر همچنان همانطور که باید به جلوی صف میروند - اما کارهای باقیمانده که میخواستید پس از تسلیم صریح انجام دهید ممکن است به دلیل سایر وظایف منابع رقیب که در صف قرار گرفتهاند بیشتر به تأخیر بیفتد.
برای مشاهده عملی این، این نسخه نمایشی Glitch را امتحان کنید — یا آن را در نسخه تعبیه شده زیر آزمایش کنید. نسخه ی نمایشی شامل چند دکمه است که می توانید روی آنها کلیک کنید و یک کادر زیر آنها که هنگام اجرای وظایف ثبت می شود. هنگامی که وارد صفحه می شوید، اقدامات زیر را انجام دهید:
- روی دکمه بالایی با برچسب اجرای دورهای وظایف کلیک کنید، که وظایف مسدود کردن را برای اجرا هر چند وقت یکبار برنامهریزی میکند. وقتی روی این دکمه کلیک میکنید، گزارش کار با پیامهایی پر میشود که عبارتند از «Ran blocking task» با
setInterval
. - در مرحله بعد، روی دکمه با عنوان حلقه اجرا کلیک کنید و در هر تکرار با
setTimeout
نمایش داده می شود .
متوجه خواهید شد که جعبه در پایین نسخه نمایشی چیزی شبیه به این را نشان می دهد:
Processing loop item 1
Processing loop item 2
Ran blocking task via setInterval
Processing loop item 3
Ran blocking task via setInterval
Processing loop item 4
Ran blocking task via setInterval
Processing loop item 5
Ran blocking task via setInterval
Ran blocking task via setInterval
این خروجی رفتار "انتهای صف کار" را نشان می دهد که هنگام تسلیم با setTimeout
رخ می دهد. حلقه ای که اجرا می شود پنج مورد را پردازش می کند و پس از پردازش هر یک با setTimeout
نتیجه می دهد.
این یک مشکل رایج در وب را نشان می دهد: برای یک اسکریپت - به ویژه یک اسکریپت شخص ثالث - غیرعادی نیست که یک تابع تایمر را ثبت کند که کار را در برخی فاصله ها اجرا می کند. رفتار "انتهای صف کار" که با بازدهی setTimeout
همراه است به این معنی است که کار از منابع دیگر وظیفه ممکن است قبل از کارهای باقی مانده ای که حلقه باید پس از بازده انجام دهد در صف قرار گیرد.
بسته به برنامه شما، این ممکن است یک نتیجه مطلوب باشد یا نباشد - اما در بسیاری از موارد، این رفتار باعث می شود توسعه دهندگان تمایلی به کنار گذاشتن کنترل رشته اصلی نداشته باشند. بازدهی خوب است زیرا تعاملات کاربر این فرصت را دارند که زودتر اجرا شوند، اما همچنین به سایر کارهای تعاملی غیر کاربر نیز اجازه میدهد تا در موضوع اصلی زمان داشته باشند. این یک مشکل واقعی است، اما scheduler.yield
می تواند به حل آن کمک کند!
scheduler.yield
وارد کنید
scheduler.yield
از نسخه 115 کروم پشت پرچم به عنوان یک ویژگی آزمایشی پلتفرم وب در دسترس بوده است. یک سوالی که ممکن است داشته باشید این است که "چرا وقتی setTimeout
از قبل این کار را انجام می دهد به یک تابع خاص نیاز دارم؟"
شایان ذکر است که تسلیم شدن یک هدف طراحی setTimeout
نبود، بلکه یک اثر جانبی خوب در برنامهریزی یک تماس برگشتی برای اجرا در نقطهای بعد در آینده بود - حتی با مقدار زمان تعیین شده 0
. با این حال، آنچه مهمتر به خاطر داشته باشید این است که تسلیم شدن با setTimeout
کار باقی مانده را به پشت صف کار ارسال میکند. به طور پیش فرض، scheduler.yield
کار باقی مانده را به جلوی صف می فرستد. این بدان معناست که کاری که میخواهید بلافاصله پس از تسلیم شدن از سر بگیرید، به وظایفی که از منابع دیگر میآیند (به استثنای تعاملات قابل توجه با کاربر) نمینشیند.
scheduler.yield
تابعی است که به thread اصلی تسلیم می شود و هنگام فراخوانی یک Promise
برمی گرداند. این بدان معنی است که می توانید آن را در یک تابع async
await
:
async function yieldy () {
// Do some work...
// ...
// Yield!
await scheduler.yield();
// Do some more work...
// ...
}
برای مشاهده scheduler.yield
در عمل، موارد زیر را انجام دهید:
- به
chrome://flags
بروید. - آزمایش ویژگیهای پلتفرم وب آزمایشی را فعال کنید. پس از انجام این کار ممکن است مجبور شوید کروم را مجددا راه اندازی کنید.
- به صفحه نمایشی بروید یا از نسخه تعبیه شده آن در زیر این لیست استفاده کنید.
- روی دکمه بالایی با عنوان Run tasks periodically کلیک کنید.
- در نهایت، روی دکمه با عنوان حلقه اجرا کلیک کنید و در هر تکرار با
scheduler.yield
نمایش داده شود .
خروجی در کادر پایین صفحه چیزی شبیه به این خواهد بود:
Processing loop item 1
Processing loop item 2
Processing loop item 3
Processing loop item 4
Processing loop item 5
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval
برخلاف نسخه آزمایشی که با استفاده از setTimeout
ارائه میشود، میتوانید ببینید که حلقه - حتی اگر بعد از هر بار تکرار نتیجه میدهد - کار باقیمانده را به پشت صف نمیفرستد، بلکه به جلوی آن میفرستد. این بهترین هر دو دنیا را به شما میدهد: میتوانید پاسخگویی ورودی را در وبسایت خود بهبود بخشید، اما همچنین اطمینان حاصل کنید که کاری که میخواهید پس از بازده انجام دهید به تأخیر نیفتد.
آن را امتحان کنید!
اگر scheduler.yield
برای شما جالب به نظر می رسد و می خواهید آن را امتحان کنید، می توانید این کار را به دو روش از نسخه 115 Chrome انجام دهید:
- اگر میخواهید با
scheduler.yield
به صورت محلی آزمایش کنید،chrome://flags
در نوار آدرس کروم تایپ کرده و وارد کنید و از منوی کشویی در بخش ویژگیهای پلتفرم آزمایشی وب، Enable را انتخاب کنید. با این کارscheduler.yield
(و هر ویژگی آزمایشی دیگر) فقط در نمونه Chrome شما در دسترس خواهد بود. - اگر میخواهید
scheduler.yield
برای کاربران واقعی Chromium در یک مبدأ در دسترس عموم فعال کنید، باید برای آزمایشی مبداscheduler.yield
ثبت نام کنید. این به شما امکان میدهد تا با خیال راحت ویژگیهای پیشنهادی را برای یک دوره زمانی معین آزمایش کنید، و به تیم Chrome اطلاعات ارزشمندی در مورد نحوه استفاده از آن ویژگیها در میدان میدهد. برای اطلاعات بیشتر در مورد نحوه کارآزماییهای مبدا، این راهنما را بخوانید .
نحوه استفاده شما از scheduler.yield
- در حالی که همچنان از مرورگرهایی که آن را اجرا نمی کنند پشتیبانی می کند - به اهداف شما بستگی دارد. می توانید از پلی فیل رسمی استفاده کنید. پلیفیل در صورتی مفید است که موارد زیر در مورد وضعیت شما صدق کند:
- شما در حال حاضر از
scheduler.postTask
در برنامه خود برای زمان بندی کارها استفاده می کنید. - شما می خواهید بتوانید اولویت های کاری را تعیین کنید.
- شما می خواهید بتوانید وظایف را از طریق کلاس
TaskController
که APIscheduler.postTask
ارائه می دهد لغو یا اولویت بندی کنید.
اگر این وضعیت شما را توصیف نمی کند، ممکن است polyfill برای شما مناسب نباشد. در این صورت، میتوانید از چند طریق بک آپ بگیرید. روش اول در صورت در دسترس بودن از scheduler.yield
استفاده می کند، اما اگر در دسترس نباشد به setTimeout
برمی گردد:
// A function for shimming scheduler.yield and setTimeout:
function yieldToMain () {
// Use scheduler.yield if it exists:
if ('scheduler' in window && 'yield' in scheduler) {
return scheduler.yield();
}
// Fall back to setTimeout:
return new Promise(resolve => {
setTimeout(resolve, 0);
});
}
// Example usage:
async function doWork () {
// Do some work:
// ...
await yieldToMain();
// Do some other work:
// ...
}
این می تواند کار کند، اما همانطور که ممکن است حدس بزنید، مرورگرهایی که scheduler.yield
پشتیبانی نمی کنند، بدون رفتار "در جلوی صف" عمل می کنند. اگر این بدان معناست که شما ترجیح میدهید به هیچ وجه بازدهی نداشته باشید، میتوانید روش دیگری را امتحان کنید که در صورت موجود بودن از scheduler.yield
استفاده میکند، اما اگر اینطور نباشد اصلا نتیجه نمیدهد:
// A function for shimming scheduler.yield with no fallback:
function yieldToMain () {
// Use scheduler.yield if it exists:
if ('scheduler' in window && 'yield' in scheduler) {
return scheduler.yield();
}
// Fall back to nothing:
return;
}
// Example usage:
async function doWork () {
// Do some work:
// ...
await yieldToMain();
// Do some other work:
// ...
}
scheduler.yield
یک افزودنی هیجان انگیز به API زمانبندی است که امیدواریم بهبود پاسخگویی را برای توسعه دهندگان نسبت به استراتژی های بازده فعلی آسان تر کند. اگر scheduler.yield
برای شما یک API مفید به نظر می رسد، لطفاً در تحقیقات ما شرکت کنید تا به بهبود آن کمک کنید و در مورد چگونگی بهبود بیشتر آن بازخورد ارائه دهید .
تصویر قهرمان از Unsplash اثر جاناتان آلیسون .