פורסם: 6 במרץ 2025
דף נראה איטי ולא מגיב כשמשימות ארוכות מעסיקות את השרשור הראשי ומונעות ממנו לבצע עבודה חשובה אחרת, כמו תגובה לקלט של משתמשים. כתוצאה מכך, אפילו אמצעי בקרה מובנים בטופס יכולים להיראות למשתמשים כאילו הם לא פועלים – כאילו הדף קפוא – שלא לדבר על רכיבים מותאמים אישית מורכבים יותר.
scheduler.yield()
היא דרך להעביר את השליטה ל-thread הראשי – כך הדפדפן יכול להריץ משימות ממתינות בעדיפות גבוהה – ואז להמשיך את ההרצה מהמקום שבו היא הופסקה. כך הדף מגיב מהר יותר, ובתמורה, משפר את המדד מאינטראקציה ועד הצגת התגובה (INP).
scheduler.yield
מציעה API ארגונומי שעושה בדיוק את מה שהוא אומר: הביצוע של הפונקציה שהוא נקרא בה מושהה בביטוי await scheduler.yield()
ועובר ל-thread הראשי, וכך מחלק את המשימה. הביצוע של שאר הפונקציה – שנקרא המשך הפונקציה – יתוזמן להרצה במשימה חדשה של לולאת אירועים.
async function respondToUserClick() {
giveImmediateFeedback();
await scheduler.yield(); // Yield to the main thread.
slowerComputation();
}
היתרון הספציפי של scheduler.yield
הוא שההמשך אחרי ההחזרה מתוזמן לפעול לפני הפעלה של משימות דומות אחרות שהוכנסו לתור על ידי הדף. הוא נותן עדיפות להמשך של משימה על פני התחלה של משימות חדשות.
אפשר גם להשתמש בפונקציות כמו setTimeout
או scheduler.postTask
כדי לחלק משימות, אבל ההמשכים האלה בדרך כלל פועלים אחרי כל המשימות החדשות שכבר הוכנסו לתור, ולכן עלולות להיות השהיות ארוכות בין ההעברה לשרשור הראשי לבין השלמת העבודה.
המשכים לפי סדר עדיפות אחרי הפסקה
scheduler.yield
הוא חלק מ-Prioritized Task Scheduling API. כמפתחי אתרים, אנחנו בדרך כלל לא מדברים על הסדר שבו לולאת האירועים מריצה משימות במונחים של עדיפויות מפורשות, אבל העדיפויות היחסיות תמיד קיימות, כמו קריאה חוזרת (callback) של requestIdleCallback
שפועלת אחרי כל הקריאות החוזרות (callbacks) של setTimeout
שנמצאות בתור, או מאזין לאירועי קלט מופעל שפועל בדרך כלל לפני משימה שנמצאת בתור עם setTimeout(callback, 0)
.
תזמון משימות לפי עדיפות פשוט הופך את זה לברור יותר, ומאפשר לכם להבין בקלות איזו משימה תפעל לפני משימה אחרת. בנוסף, תוכלו לשנות את סדר הביצוע הזה על ידי שינוי העדיפויות, אם צריך.
כמו שצוין, ההמשך של ביצוע פונקציה אחרי החזרת ערך עם scheduler.yield()
מקבל עדיפות גבוהה יותר מאשר התחלה של משימות אחרות. העיקרון המנחה הוא שהמשך של משימה צריך לפעול קודם, לפני שמתקדמים למשימות אחרות. אם המשימה היא קוד שמתנהג בצורה תקינה ומפנה את המעבד באופן תקופתי כדי שהדפדפן יוכל לבצע פעולות חשובות אחרות (כמו תגובה לקלט של משתמשים), לא צריך להעניש אותה על ההפניה על ידי מתן עדיפות נמוכה יותר למשימות דומות אחרות.
הנה דוגמה: שתי פונקציות, שהוכנסו לתור להפעלה במשימות שונות באמצעות setTimeout
.
setTimeout(myJob);
setTimeout(someoneElsesJob);
במקרה הזה, שתי הקריאות setTimeout
נמצאות זו לצד זו, אבל בדף אמיתי יכול להיות שהן יופעלו במקומות שונים לגמרי, למשל סקריפט של צד ראשון וסקריפט של צד שלישי שמגדירים באופן עצמאי את הפעולות שיש לבצע, או שתי משימות מרכיבים נפרדים שמופעלות עמוק בתוך מתזמן המשימות של המסגרת.
כך יכולה להיראות העבודה הזו בכלי הפיתוח:
myJob
מסומנת כמשימה ארוכה, שמונעת מהדפדפן לבצע פעולות אחרות בזמן שהיא פועלת. אם מניחים שהיא מגיעה מסקריפט של צד ראשון, אפשר לפרק אותה:
function myJob() {
// Run part 1.
myJobPart1();
// Yield with setTimeout() to break up long task, then run part2.
setTimeout(myJobPart2, 0);
}
הפעולה myJobPart2
תוכננה לפעול עם setTimeout
בתוך myJob
, אבל התזמון הזה מתבצע אחרי שsomeoneElsesJob
כבר תוכננה, ולכן כך ייראה הביצוע:
פיצלנו את המשימה באמצעות setTimeout
כדי שהדפדפן יוכל להגיב באמצע של myJob
, אבל עכשיו החלק השני של myJob
פועל רק אחרי ש-someoneElsesJob
מסתיים.
במקרים מסוימים זה בסדר, אבל בדרך כלל זה לא אופטימלי. myJob
נכנע לשרשור הראשי כדי לוודא שהדף יוכל להמשיך להגיב לקלט של המשתמש, לא כדי לוותר על השרשור הראשי לחלוטין. במקרים שבהם someoneElsesJob
איטי במיוחד, או שהרבה משימות אחרות מלבד someoneElsesJob
תוכננו גם הן, יכול לעבור זמן רב לפני שהחלק השני של myJob
יופעל. יכול להיות שזו לא הייתה הכוונה של המפתח כשהוא הוסיף את setTimeout
ל-myJob
.
מזינים scheduler.yield()
, וכך המשך ההפעלה של כל פונקציה שקוראת לה יקבל עדיפות מעט גבוהה יותר בתור מאשר התחלה של משימות דומות אחרות. אם משנים את myJob
כך שישתמש בו:
async function myJob() {
// Run part 1.
myJobPart1();
// Yield with scheduler.yield() to break up long task, then run part2.
await scheduler.yield();
myJobPart2();
}
עכשיו הביצוע נראה כך:
עדיין יש לדפדפן הזדמנות להגיב, אבל עכשיו ההמשך של המשימה myJob
מקבל עדיפות על פני התחלת המשימה החדשה someoneElsesJob
, ולכן myJob
תושלם לפני שsomeoneElsesJob
תתחיל. הגישה הזו קרובה יותר לציפייה להעברה של ה-thread הראשי כדי לשמור על היענות, ולא לוויתור על ה-thread הראשי לחלוטין.
העברת עדיפות בירושה
כחלק מ-Prioritized Task Scheduling API (ממשק API לתזמון משימות לפי סדר עדיפויות), scheduler.yield()
משתלב היטב עם העדיפויות המפורשות שזמינות ב-scheduler.postTask()
. אם לא מגדירים במפורש עדיפות, פונקציית scheduler.yield()
בתוך קריאה חוזרת (callback) של scheduler.postTask()
תפעל באופן דומה לדוגמה הקודמת.
עם זאת, אם מוגדרת עדיפות, למשל אם משתמשים בעדיפות נמוכה 'background'
:
async function lowPriorityJob() {
part1();
await scheduler.yield();
part2();
}
scheduler.postTask(lowPriorityJob, {priority: 'background'});
ההמשך יתוזמן עם עדיפות גבוהה יותר ממשימות אחרות של 'background'
– כך תקבלו את ההמשך המתוזמן בעדיפות לפני כל עבודה ממתינה של 'background'
– אבל עדיין עם עדיפות נמוכה יותר ממשימות אחרות שמוגדרות כברירת מחדל או עם עדיפות גבוהה. העבודה תמשיך להיות עבודה של 'background'
.
המשמעות היא שאם תזמנו עבודה בעדיפות נמוכה עם 'background'
scheduler.postTask()
(או עם requestIdleCallback
), ההמשך אחרי scheduler.yield()
ימתין גם הוא עד שרוב המשימות האחרות יושלמו וה-thread הראשי יהיה במצב idle כדי לפעול. זה בדיוק מה שרוצים לקבל מ-yielding בעבודה בעדיפות נמוכה.
איך משתמשים ב-API?
בשלב הזה, scheduler.yield()
זמין רק בדפדפנים שמבוססים על Chromium, לכן כדי להשתמש בו צריך לזהות את התכונה ולחזור לשיטה משנית להעברת השליטה בדפדפנים אחרים.
scheduler-polyfill
הוא polyfill קטן ל-scheduler.postTask
ול-scheduler.yield
, שמשתמש באופן פנימי בשילוב של שיטות כדי לדמות הרבה מהיכולות של ממשקי ה-API לתזמון בדפדפנים אחרים (אבל אין תמיכה ב-scheduler.yield()
priority inheritance).
אם רוצים להימנע מ-polyfill, אפשר להשתמש ב-setTimeout()
כדי להשיג את התוצאה הרצויה, או אפילו לא להשתמש ב-yield בדפדפנים לא נתמכים אם זה לא מקובל. מידע נוסף מופיע בscheduler.yield()
מאמר בנושא אופטימיזציה של משימות ארוכות ב-Optimize.
אפשר להשתמש בסוגים של wicg-task-scheduling
גם כדי לקבל בדיקת סוגים ותמיכה ב-IDE אם אתם מזהים תכונות של scheduler.yield()
ומוסיפים חלופה בעצמכם.
מידע נוסף
במאמרים scheduler.yield()
וPrioritized Task Scheduling ב-MDN יש מידע נוסף על ה-API ועל האינטראקציה שלו עם תעדוף משימות ועם scheduler.postTask()
.
כדי לקבל מידע נוסף על משימות ארוכות, על ההשפעה שלהן על חוויית המשתמש ועל הפעולות שצריך לבצע לגביהן, אפשר לקרוא על אופטימיזציה של משימות ארוכות.