תאריך פרסום: 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
וויתר על שרשור ה-main כדי לוודא שהדף יוכל להגיב לקלט של המשתמשים, לא כדי לוותר על שרשור ה-main לגמרי. במקרים שבהם 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 הראשי יהיה פנוי לפעולה. זה בדיוק מה שרוצים מהענקת עדיפות לעבודה בעדיפות נמוכה.
איך משתמשים ב-API?
בשלב הזה, scheduler.yield()
זמין רק בדפדפנים המבוססים על Chromium, כך שצריך לזהות את התכונה ולהשתמש בדרך חלופית להעברת בקשות לדפדפנים אחרים.
scheduler-polyfill
הוא polyfill קטן ל-scheduler.postTask
ול-scheduler.yield
שמשתמש באופן פנימי בשילוב של שיטות כדי לדמות חלק גדול מהיכולות של ממשקי ה-API לתזמון בדפדפנים אחרים (אבל אין תמיכה בירושה של תעדוף scheduler.yield()
).
אם אתם רוצים להימנע משימוש ב-polyfill, אחת מהשיטות היא להשתמש ב-yield באמצעות setTimeout()
ולקבל את אובדן ההמשך בעדיפות גבוהה, או אפילו לא להשתמש ב-yield בדפדפנים לא נתמכים אם זה לא מקובל עליכם. מידע נוסף זמין במסמכי העזרה של scheduler.yield()
בנושא אופטימיזציה של משימות ארוכות.
אפשר להשתמש בסוגי wicg-task-scheduling
גם כדי לקבל בדיקת סוגים ותמיכה ב-IDE אם אתם מזוהים תכונות scheduler.yield()
ומוסיפים חלופה חלופית בעצמכם.
מידע נוסף
למידע נוסף על ה-API ועל האינטראקציה שלו עם עדיפות המשימות ו-scheduler.postTask()
, אפשר לעיין במסמכים scheduler.yield()
ותזמון משימות לפי תעדוף ב-MDN.
מידע נוסף על משימות ארוכות, על ההשפעה שלהן על חוויית המשתמש ועל הפעולות שאפשר לבצע לגביהן זמין במאמר אופטימיזציה של משימות ארוכות.