תאריך פרסום: 6 במרץ 2025
הדף נראה איטי ולא מגיב כשמשימות ארוכות שומרות על שרשור הראשי עסוק, ומונעות ממנו לבצע משימות חשובות אחרות, כמו תגובה לקלט של משתמשים. כתוצאה מכך, גם רכיבי בקרה מובנים של טפסים יכולים להיראות לא תקינים למשתמשים – כאילו הדף קפוא – שלא לדבר על רכיבים מותאמים אישית מורכבים יותר.
scheduler.yield()
היא דרך להעביר את העדיפות לשרשור הראשי – ומאפשרת לדפדפן להריץ משימות בהמתנה בעדיפות גבוהה – ולאחר מכן להמשיך את הביצועים מהמקום שבו הם הופסקו. כך הדף יהיה תגובה יותר מהר, וכתוצאה מכך יעזור לשפר את הזמן מאינטראקציה ועד לציור הבא (INP).
scheduler.yield
מציע ממשק API ארגונומי שעושה בדיוק את מה שהוא אומר: ביצוע הפונקציה שבה היא נקראת מושהה בביטוי await scheduler.yield()
ומעביר את הבעלות ל-thread הראשי, ומפרק את המשימה. ביצוע שאר הפונקציה – שנקרא המשך הפונקציה – יהיה מתוזמן לפעול במשימה חדשה של לולאת אירועים.
async function respondToUserClick() {
giveImmediateFeedback();
await scheduler.yield(); // Yield to the main thread.
slowerComputation();
}
היתרון הספציפי של scheduler.yield
הוא שהמשך לאחר ה-yield מתוזמן לפעול לפני הפעלת משימות דומות אחרות שהדף הכניס לתור. המערכת נותנת עדיפות להמשך משימות על פני התחלת משימות חדשות.
אפשר גם להשתמש בפונקציות כמו setTimeout
או scheduler.postTask
כדי לפצל משימות, אבל הפונקציות האלה מריצות את ההמשכות בדרך כלל אחרי משימות חדשות שכבר נמצאות בתור, וכך עלולות לגרום לעיכובים ארוכים בין העברת השליטה לשרשור הראשי לבין השלמת העבודה.
המשכות לפי תעדוף אחרי הענקת הבעלות
scheduler.yield
הוא חלק מ-Prioritized Task Scheduling API. כמפתחי אינטרנט, אנחנו בדרך כלל לא מדברים על הסדר שבו לולאת האירועים מפעילה משימות במונחים של סדר עדיפויות מפורש, אבל סדר העדיפויות היחסי תמיד קיים, למשל קריאה חוזרת (callback) של requestIdleCallback
שתופעל אחרי כל קריאה חוזרת של 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
וויתר על שרשור ה-UI הראשי כדי לוודא שהדף יוכל להגיב לקלט של המשתמשים, לא כדי לוותר על שרשור ה-UI הראשי לגמרי. במקרים שבהם 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()
. בלי להגדיר עדיפות באופן מפורש, קריאה חוזרת (callback) של scheduler.yield()
בתוך 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 הראשי יהיה פנוי לפעולה. זה בדיוק מה שרוצים כשמשתמשים ב-yield במשימה בעדיפות נמוכה.
איך משתמשים ב-API?
בשלב הזה, scheduler.yield()
זמין רק בדפדפנים המבוססים על Chromium, כך שצריך לזהות את התכונה ולהשתמש בדרך חלופית להעברת בקשות לדפדפנים אחרים.
scheduler-polyfill
הוא polyfill קטן ל-scheduler.postTask
ול-scheduler.yield
שמשתמש באופן פנימי בשילוב של שיטות כדי לדמות הרבה מהיכולות של ממשקי ה-API לתזמון בדפדפנים אחרים (אבל אין תמיכה בירושה של תעדוף scheduler.yield()
).
אם אתם רוצים להימנע משימוש ב-polyfill, אחת מהשיטות היא להשתמש ב-yield באמצעות setTimeout()
ולהשלים עם אובדן ההמשך בעדיפות גבוהה, או אפילו לא להשתמש ב-yield בדפדפנים שלא תומכים ב-yield, אם זה לא מקובל עליכם. מידע נוסף זמין במסמכי העזרה של scheduler.yield()
בנושא אופטימיזציה של משימות ארוכות.
אפשר להשתמש בסוגים של wicg-task-scheduling
גם כדי לקבל בדיקת סוגים ותמיכה ב-IDE אם אתם מזוהים תכונות scheduler.yield()
ומוסיפים חלופה חלופית בעצמכם.
מידע נוסף
למידע נוסף על ה-API ועל האינטראקציה שלו עם תעדוף המשימות ו-scheduler.postTask()
, אפשר לעיין במסמכים scheduler.yield()
ו-תזמון משימות לפי תעדוף ב-MDN.
מידע נוסף על משימות ארוכות, על ההשפעה שלהן על חוויית המשתמש ועל הפעולות שאפשר לבצע לגביהן זמין במאמר אופטימיזציה של משימות ארוכות.