ההקדמה אפליקציות אינטרנט מודרניות יכולות להרגיש כבדות.לפעמים, רק פונקציה אחת של JavaScript שנמשכת זמן רב מספיקה כדי להקפיא את הממשק, ולהשאיר את המשתמשים מתוסכלים ולא בטוחים אם האפליקציה עדיין פועלת או קפאה. יש לנו שיטה קטנה אך יעילה: זה מאפשר למשתמש להפסיק את הביצוע, נותן לדפדפן את ההזדמנות להתמודד עם משימות חשובות יותר (כגון לחיצה או כתיבה), ולאחר מכן להמשיך בדיוק איפה הם הפסיקו. בואו לגלות את המגמות הישנות ולראות איך עושה את החיים קלים יותר. תכנון תפקידים API תגית: תגית: תגית: Main Thread scheduler.yield() מהי תרומה (תרומה) אז, מה זה זוהי שיטה של אינטראקציה מהחדש שיטה זו מאפשרת לך, בתור מפתח, להפסיק את ביצועי JavaScript שלך ולהפוך באופן מפורש את השליטה בחזרה ל- JavaScript. כך שהוא יכול להתמודד עם משימות חשובות אחרות, כגון אינטראקציות משתמשים, קליקים, כתיבה וכו ', ולאחר מכן להמשיך בביצוע ממקום שבו הפסקת. אתה אומר לדפדפן: scheduler.yield() לוח הזמנים לוח הזמנים של API. Main Thread scheduler.yield() "חכה, קח נשימה, בואו להפסיק את המשימה הנוכחית ולהתמקד במשימות אחרות לא פחות או יותר חשובות. "חכה, קח נשימה, בואו להפסיק את המשימה הנוכחית ולהתמקד במשימות אחרות לא פחות או יותר חשובות. זה הופך את הדף שלך תגובה יותר, במיוחד בעת ביצוע משימות JavaScript ארוכות או כבדות. הכל תלוי באיזו מהירות הדפדפן מגיב להכנסת המשתמש. אינטראקציה עם Paint Next (INP) טרמינולוגיה לפני שאתה לטבול עמוק יותר, בואו נעבור במהירות על כמה מונחים בסיסיים שישמשו לאורך המאמר. Main Thread – זהו המקום המרכזי שבו הדפדפן עושה את רוב העבודה שלו. משימה ארוכה – זהו כל משימה של JavaScript שמחזיקה את החוט הראשי עסוק במשך זמן רב מדי, בדרך כלל יותר מ-50 מילישניות. משימה לחסום – היא פעולה סינכרונית על החוט הראשי המונעת מהדפדפן לעבד דברים חשובים אחרים, כגון תגובה לחיצות או עדכון של המשתמש. את הבעיה כדי להבין את היופי של , אתה צריך קודם להבין איזו בעיה היא מנסה לפתור. JavaScript פועל על חוט יחיד. זה אומר שהוא יכול לעשות רק דבר אחד בכל פעם. אם הקוד שלך שומר את החוט עסוק, כל השאר – rendering, לחיצה על כפתור, כתיבת כתיבה צריך לחכות. בעולם אידיאלי, אתה תמיד לחלק משימות כבדות לחתיכות קטנות. אבל המציאות היא מבולבלת. אתה מתמודד עם קוד עתיק, סקריפטים של צד שלישי, או חישובים כבדים בלתי נמנעים. וכאשר זה קורה, המשתמשים נתקעו בדפים קפואים. scheduler.yield() מודל ביצועי JavaScript. בתור חידוש מהיר, הנה תרשים דוגמה של איך JavaScript מעבד משימות – במילים אחרות, איך אני בטוח שרבים מכם ראו תרשימים כאלה בעבר – שורות המשימות, זרם האירועים, צלחת השיחות. JavaScript Execution Model בואו נעבור דרך הרעיונות העיקריים צעד אחר צעד: כל הקוד הסינכרוני הולך ישירות ל-Call Stack והוא פועל שורה אחרי שורה, פונקציה אחרי פונקציה.הוא עוקב אחר העיקרון LIFO - אחרון בפעם הראשונה. פעולות אסינכרוניות (כגון setTimeout, fetch) מתבצעות מחוץ ל- Main Thread – על ידי API האינטרנט (מסופק על-ידי הדפדפן או הסביבה). ברגע שהם מתבצעים, הם לא חוזרים ישירות ל-Call Stack. כאשר קובץ ההתקשרות ריק, מעגל האירועים בודק את ציון המיקרו-משימות ומפעיל את כל המיקרו-משימות אחת אחרי השנייה. רק לאחר מכן, הוא לוקח משימה אחת מאקרומה מהצעדה ומפעיל אותה. אם, במהלך התהליך, מוסיפים microtasks חדשים, הם פועלים לפני macrotasks הבא, כך microtasks תמיד מקבלים עדיפות. מעגל זה ממשיך: כל microtasks → one macrotask → repeat. קוד סינכרון חדש נכנס ל-Call Stack כאשר משימות חדשות מגיעות, כגון משתמש לוחץ על כפתור, תסריט חדש פועל, או כאשר מיקרו-משימות או משימות מאקרו פועלות את התקשרו. זהו הסבר קצר ושטחי מאוד, רק כדי להזכיר לכם איך זה עובד. תיאור הבעיה עכשיו שאתם מחדשים את ההבנה של איך JavaScript מבצע משימות, בואו נסתכל מקרוב על הבעיה האמיתית שמגיעה עם מודל זה. הבעיה היא פשוטה: כאשר משימה לוקחת יותר מדי זמן על החוט הראשי, היא חוסמת את כל השאר – אינטראקציות משתמש, ביצוע עדכונים ואנימציות. זה מוביל ל- UI מקפיא ותגובה נמוכה. המחשבה הראשונה הנכונה עשויה להיות: "טוב, פשוט לא לכתוב פונקציות ארוכות או כבדות, וזהו. הבעיה נפתרה!" וכן, זה נכון - בעולם אידיאלי, תמיד הייתם מחלקים קוד כבד לחלקים קטנים יותר, אופטימיזציה הכל, ולמנוע חסימת החוט הראשי. אבל בואו נהיה כנים - רבים מאית לשם כך, אנו יוצרים פונקציה שנקראת הפונקציה פועלת כמשימה לחסום את החוט הראשי לתקופה מסוימת של זמן.הפונקציה מדגימה סוג זה של חישוב "כבד" על כל אלמנט של המסדרון. blockingTask() למרבה הצער, בלוקי קוד אינם מציגים מספרים של שורות.בהסברים שלי, אני מתייחס לפעמים לשורות ספציפיות (לדוגמה, "שורה 5 עושה X"). למרבה הצער, בלוקי קוד אינם מציגים מספרים של שורות.בהסברים שלי, אני מתייחס לפעמים לשורות ספציפיות (לדוגמה, "שורה 5 עושה X"). function blockingTask(ms = 10) { const arr = []; const start = performance.now(); while (performance.now() - start < ms) { // Perform pointless computation to block the CPU. arr.unshift(Math.sqrt(Math.random())); } return arr; } אין שום דבר דמיוני על הפונקציה, הנה כל מה שהיא עושה: הוא מקבל ארגומנט - מספר המילישניות.זהו הזמן המינימלי שהפונקציה תפעיל, ובכך תופס את החוט הראשי. זה יוצר ערכת ריקה. זה יוצר זמן התחלה (כמו זמן נוכחי). לאחר מכן, לרוץ במשך זמן מה עד שהזמן המוגדר עבר. בתוך המעגל, הוא פשוט עושה חישובים אקראיים וחסרי משמעות כדי לסמול את העומס. לבסוף, הוא מחזיר את התוצאה של החישובים. הפונקציה אינה עושה שום דבר שימושי, אבל היא מדגימה תרחיש בעולם האמיתי של עומס כבד. תארו לעצמכם מצב נפוץ שבו אתה צריך לסובב דרך מערך של נתונים וליישם את העבודה הכבדה על כל פריט. כדי לעשות זאת, אנו יוצרים תפקוד : heavyWork() function heavyWork () { const data = Array.from({ length: 200 }, (_, i) => i) const result = [] for (let i = 0; i < data.length; i++) { result.push(blockingTask(10)) } return result; } שבו מתרחש הדבר הבא: בשורה 2, זה יוצר מערך של 200 פריטים, רק מספרים מ 0 עד 199. אני רוצה לציין כי 200 פריטים הם לא כל כך הרבה, אבל זה יהיה מספיק כדי לראות את המהות של הבעיה. לאחר מכן, נוצר סדרת "תוצאה" ריקה חדשה כדי לאחסן את הערכים המעובדים. קו 5 מכריז על מעגל שעובר לאורך כל אורך המסדרת הנתונים. בתוך המעגל, אנו מפעילים את הפונקציה BlockingTask() , סימולציה של 10 מילישניות של עבודה עבור כל אלמנט, והתוצאה מוסיפה את המסדרת "תוצאה". שוב, אני רוצה להזכיר לך כי, עבור הדמו, הפונקציה BlockingTask() לא נושאת שום עומס סמנטי. בסופו של דבר, הוא מחזיר את המסגרת המתקבלת. רק 10 מילישניות לכל אלמנט, ורק 200 אלמנטים – אבל יחד, הם חוסמים את החוט הראשי למשך 2 שניות שלמות. הבעיה היא הפגנה. עכשיו זה הזמן להסתכל על הבעיה לא רק בתיאוריה, אלא בפעולה.זה לא דומיין מלא עדיין - לחשוב על זה כמו חזותית פשוטה כדי לעזור לך לראות בבירור את הבעיה. הנה מה שאתם רואים: החלון השמאלי, בשם "Configuration", מאפשר לך להפעיל את חסימת החוט הראשי - כלומר אם הפונקציה BlockingTask() פועלת למעשה. חלון שנקרא "משימה כבדה" פועל בפונקציה heavyWork() זה הוא זה שמעבד מסדרה באמצעות blockingTask() על כל אלמנט אם חוסם החוט העיקרי מופעל. וחלונית בשם "Logger" רק רושמת את הזמן הנוכחי לקונסולה, כולל מילישניות. בואו נראה מה קורה כאשר החסימה מופעלת, ולכן המשימות קלות מאוד.זה רק מעגל על מערך של 200 אלמנטים, ללא כל חישובים מורכבים. Main Thread מה שאתה רואה: המשתמש לוחץ על הכפתור "OK" - הפונקציה heavyWork() פועלת, ומחזירה באופן מיידי.זה מצוין על ידי ההודעה HEAVY_TASK_DONE בקונסולה, ולאחר מכן התוצאה - סדרת מספרים. לאחר מכן, המשתמש לוחץ על כפתור "Log" שלוש פעמים, כדי להקליט את הזמן הנוכחי לקונסולה - סימני זמן מופיעים מיד, עם הבדל קל בזמן. המשתמש פועל את הפונקציה heavyWork() שוב, ושוב, תגובה מיידית. לבסוף, המשתמש סוגר שני חלונות, אשר למעשה פשוט מסיר את האלמנטים האלה מ- DOM. במקרה זה, הכל מרגיש מהיר ומגיב. לדפדפן אין בעיה לנהל את האינטראקציות, כי החוט העיקרי נשאר חופשי. עכשיו, בואו לאפשר את על מנת להבטיח כי עבור כל אחד מהמרכיבים של המכשיר, הפונקציה תתקשר עם עיכוב של רק 10 מילישניות. Main Thread blockingTask() ועכשיו אתה יכול לצפות כי האינטראקציה של המשתמש עם אלמנטים UI הפך פחות חלקי, UI קפואות הופיעו. המשתמש לוחץ על הכפתור "OK", ובכך פותח את הפונקציה heavyWork() והעיכוב הראשון שמתרחש הוא שהכפתור "OK" נשאר בלחוץ באופן חזותי. למה? בגלל הדפדפן לא יכול לצבוע מחדש בעוד heavyWork() עדיין חוסם את החוט הראשי. במהלך הזמן הזה, המשתמש לוחץ על כפתור "Log" ארבעה פעמים - שום דבר לא קורה. הקליקים נרשמים ומטפלים שלהם הוסיפו לקו, אבל הדפדפן לא יכול להגיב. רק לאחר heavyWork() נגמר אתה רואה את היציאה הקונסולה: תחילה את תוצאה heavyWork(), ואז את ארבעת סימני הזמן - כולם מודפסים בסדרה. לאחר מכן, המשתמש לוחץ על הכפתור "OK" שוב. אותו התנהגות – לחצו על הכפתור. ואז, בעוד המשימה heavyWork() פועלת, הוא מנסה לסגור חלון על ידי לחיצה על סמל "X" שלוש פעמים. ולבסוף, עוד ניסיון אחד להפעיל heavyWork() ולסגור את החלון האחרון. מה מראה זה? הדמו הפשוט הזה מראה כמה זמן משימות חוסמות את היכולת של הדפדפן להגיב לפעולות המשתמש. למרות שכל שיחה חוסמת לוקחת רק 10 מילישניות, שרשרת 200 מהם ביחד תוביל לקירור של 2 שניות. המשתמש לא יכול לקיים אינטראקציה עם כפתורים, הממשק לא מחדש. אירועים מקבלים שורות, אבל לא מעובדים עד שהקלטת השיחות ברורה. זה לא רק בעיה ביצועים – זה בעיה של ניסיון המשתמש. וזה בדיוק הסוג של בעיה שאנחנו רוצים לפתור – באופן אידיאלי, ללא צורך לחלק באופן ידני את ההיגיון שלנו לעשרות שיחות חזרה. פתרון הבעיה עכשיו שאתה מבין את הבעיה, בואו נדבר על פתרונות אפשריים. כמובן, האסטרטגיה הטובה ביותר היא להימנע משימות ארוכות במקום הראשון על ידי שמירה על קוד יעיל ולשבור דברים מוקדם. אבל, כפי שראיתם, דברים קורים. בין אם זה קוד עתיק, חישובים בלתי נמנעים, או פשוט לא מספיק זמן כדי לאופטימיזציה, לפעמים, אתה צריך להתמודד עם זה. הופיעו, סדנאות שונות וטריקים הומצאו כדי לשפר את התגובה, אבל הרעיון המרכזי מאחורי כל אלה – ומאחורי וגם - זה די פשוט: Prioritized Task Scheduling API scheduler.yield() לחלק את המשימה לחתיכות קטנות יותר או נקראות חתיכות. מדי פעם, הפסקה כדי לאפשר לדפדפן לקחת את נשימתו. במילים אחרות, אתה נותן את החוט הראשי הזדמנות להפעיל משימות דחופות יותר, כגון אינטראקציות משתמש או rendering עדכונים, ולאחר מכן אתה חוזר כדי להשלים את העבודה שלך. הנה מה המושג של הפונקציה נראית כמו ב-Pseudocode: heavyWork() function heavyWork() { // Do heavy work... /** * Take a breather! * Yield the execution to the Main Thread... * */ // Continue to do heavy work... } מה קורה כאן: אתה מבצע חלק מהתפקיד שלך. לאחר מכן, אתה מפסיק, ומאפשר לדפדפן להתמודד עם משימות אחרות בעלות עדיפות גבוהה (כגון עדכוני UI). המשיכו להפעיל את הפונקציה משם היא הפסיקה. גישות פתרון בעיות ישנות. לפני הטריק הנפוץ ביותר להתמודדות עם משימות בלוק ארוכות היה להשתמש על-ידי התקשר אליו עם 0 (אפס) עיכוב, אתה מוסיף את משימת ההקלטה שלו לסוף תן למשתמש לבצע את המשימות האחרות קודם לכן, במילים אחרות, אתה אומר לדפדפן: scheduler.yield() setTimeout() macrotasks "הפעילו את הקוד הזה מאוחר יותר, לאחר שעשיתם את כל השאר". "הפעילו את הקוד הזה מאוחר יותר, לאחר שעשיתם את כל השאר". ככה אתה יכול לתת את החוט הראשי נשימה קצרה בין חתיכות של עבודה קשה. הפונקציה עשויה להיראות כך באמצעות גישה זו: heavyWork() async function heavyWork() { // Yield to Main Thread to avoid UI blocking before heavy work await new Promise(resolve => setTimeout(resolve, 0)) const data = Array.from({ length: 200 }, (_, i) => i) const result = [] // Interval at which execution will be yielded to the main thread (approx. ~ 25%). const yieldInterval = Math.ceil(data.length / 4) for (let i = 0; i < data.length; i++) { // Yield control to Main Thread to update UI and handle other tasks. if (i % yieldInterval === 0) { await new Promise(resolve => setTimeout(resolve, 0)) } result.push(threadBlockingEnabled ? blockingTask(10) : data[i]) } return result } בואו נדבר על מה שקורה כאן: שורה 3: נוצר הבטחה ומבצע שלה פועל מיד, מתכנן שורה setTimeout() עם עיכוב אפס. ההחזרה של timeout (היא פותרת את הבטחה) מופיעה בסוף שורה ה-macrotask. עקב ההמתנה, שאר הפונקציה async מופסקת. מבחינה טכנית, ההמשך הזה מופיע בשורה ה-microtask, ומחכה שהבטחה תיפתר. מנוע ה-JavaScript בודק את ה-Call Stack – ברגע שהוא ריק, פול האירוע נכנס. ראשית, הוא מסתכל על שורה ה-microtask – אבל מאחר שהבטחה לא נפתרה עדיין, אין מה לרוץ. לאחר מכן, פול האירוע בוחר את ה-macrotask מהשורה שורה 9: אנו מחושבים כמה פעמים אנחנו רוצים להשיג את החוט הראשי, בערך כל 25% מהעבודה. שורות 13-15: בתוך המעגל, אם התנאים לתקופה של ניתנות עומדים, ביצוע מועבר לתוך החוט הראשי, כלומר, טכניקת setTimeout() חוזרת, ומאפשרת למשתמש להתמודד עם התהליך של הדפדפן או לערוך מחדש את הממשק. למעשה, גישה זו עובדת – היא פשוטה יחסית ומשפרת את התגובה. לא נבנה לתזמון מדויק, הוא מטיל משימות בסוף ציון המקרו, וכל דבר שכבר קיים באותו ציון עלול לדחות את ההמשך שלך. setTimeout() לדוגמה, נניח שחלק אחר של הדף משתמש לבצע את המשימות באופן קבוע: setInterval() setInterval(() => { /* Another heavy work... */ }) async function heavyWork() { // Yield to Main Thread to avoid UI blocking before heavy work await new Promise(resolve => setTimeout(resolve, 0)) const data = Array.from({ length: 200 }, (_, i) => i) const result = [] // Interval at which execution will be yielded to the main thread (approx. ~ 25%). const yieldInterval = Math.ceil(data.length / 4) for (let i = 0; i < data.length; i++) { // Yield control to Main Thread to update UI and handle other tasks. if (i % yieldInterval === 0) { await new Promise((resolve, reject) => setTimeout(resolve, 0)) } result.push(threadBlockingEnabled ? blockingTask(10) : data[i]) } return result } עכשיו המשימה שלך - החלק הבא של פונקציה – עשויה להיעכב על ידי אחד או יותר משיחות הפסקה אלה.הדפדפן רק פועל כל דבר הבא בשורה, ואתה לא שולט על הסדר. סוג של מאפשר לך להפוך, אתה לא יודע בדיוק מתי תקבל את השליטה בחזרה. heavyWork() setTimeout() ישנן דרכים אחרות להתמודד עם המצב. פונקציה, המאפשרת לך לתזמן את העבודה ממש לפני ההחלפה הבאה. , ויש לו חסרונות דומים, או זה לא ממש חלופה, אבל טוב עבור רקע, עבודה פחות חשובה, זה עוזר החוט העיקרי להיות חופשי עבור משימות קריטיות יותר. באופן כללי, אנחנו יכולים לדון באסטרטגיות אחרות לפתרון ולמנוע בעיות כאלה. הביאו אותו לשולחן. requestAnimationFrame() setTimeout() requestIdleCallback() scheduler.yield() תגיות » תגיות » תגיות » תגיות – היא דרך מודרנית להפסיק את הביצוע, ולתת שליטה על החוט הראשי, המאפשרת לדפדפן לבצע כל עבודה בעלת עדיפות גבוהה המתמדת, ולאחר מכן להמשיך בביצוע ממקום שהפסיק. התרגום מגיע, הביצוע של הפונקציה הנוכחית שבה הוא נקרא הוא מופסק, ומאפשר שליטה על החוט הראשי, ובכך לשבור, או להפסיק, את המשימה הנוכחית. scheduler.yield() await scheduler.yield() היופי של האם ההמשך לאחר הוא נשאר בחזית המדרגה, והוא מתוכנן לרוץ. כל המשימות הלא חיוניות האחרות שהיו בשורה התחתונה. הגישה הזו היא עם , המשכים אלה פועלים בדרך כלל לאחר משימות חדשות שכבר היו בשורה, וייתכן שנגרמו עיכובים ארוכים בין ההפצה לתוך החוט הראשי לבין השלמתם. scheduler.yield() scheduler.yield() BEFORE setTimeout() setTimeout() הדיאגרמה הבאה מציינת כיצד שלושת הגישות משוותות בפועל: In the first example, without yielding to the main thread: At first, the long " " runs uninterrupted, blocking the main thread and UI accordingly. Then, a user event is processed – a button click triggered during the execution of " ". And finally, " " is executed – callback scheduled earlier or during the execution of the long task. Task 1 Task 1 Task 2 setTimeout() In the second example, using as a yielding to the main thread: The execution queue is different. At first, the long " " runs. Then, when the yield to the main thread happens, " " pauses to let the browser breathe, and the button click is processed. But after the button click is processed, the callback will be executed first, which could have been scheduled in advance or during the execution of " ". And finally, only after that, the continuation of " " will be executed. setTimeout() Task 1 Task 1 setTimeout() Task 1 Task 1 In the last example, using : After the long " " has been paused and the user click event has been processed, then the continuation of " " is prioritized and runs before any queued tasks. scheduler.yield() Task 1 Task 1 setTimeout() In summary, is a more intelligent and predictable way to give the main thread breathing room. It avoids the risk of your code being pushed too far back in the queue and helps maintain performance and responsiveness, especially in complex applications. scheduler.yield() את העדיפויות אז מה גורם להבדל כזה בהתנהגות? זה הכל על עדיפויות! בתור מפתחים, אנחנו בדרך כלל לא חושבים על הסדר של ביצוע משימות במעגל האירועים במונחים של עדיפויות. ו אבל אם תסתכל עמוק יותר, תוכל להבחין כי יש גם עדיפויות משותפות במשחק.לדוגמה, מנהל לחיצה על כפתור, שנוצר על ידי פעולה של משתמש, יפעיל בדרך כלל לפני תגית: למרות ששניהם כמו שאמרתי קודם, זהו חלק מה – ממשק נרחב ועשיר בתכונות שמגיע לדיון מלא נפרד משלו והוא ברור מעבר לתחום הדיון הזה, עם זאת, חשוב לציין אחד התכונות המרכזיות שלו: ייצוג של מודל עדיפות משימה ברור. API לוח הזמנים של מטלות עדיפות פשוט עושה את העדיפויות האלה ברורות, מה שהופך אותו קל יותר לקבוע איזו משימה תבוצע תחילה, ומאפשר התאמת עדיפויות כדי לשנות את הסדר של ביצוע, אם יש צורך. microtasks macrotasks setTimeout() macrotasks scheduler.yield() Prioritized Task Scheduling API "בלוק של משתמשים" - המשימות בעלות העדיפות הגבוהה ביותר המשפיעות ישירות על אינטראקציה של משתמשים, כגון ניהול קליקים, לחיצות ופעולות UI קריטיות. "נראה על ידי המשתמש" – משימות המשפיעות על נראות UI או על תוכן, אך אינן קריטיות עבור כניסה מיידית. "הרקע" – משימות שאינן דחופות, וניתן לדחות אותן בבטחה מבלי להשפיע על חוויית המשתמש הנוכחית, ולא נראות למשתמש. על ידי default, יש לך A » עדיף גם כן, לחשוף את שיטה, המיועדת לתזמן משימות עם עדיפות מסוימת מהעלייה.בעוד זה לא הולך לפרטים על שיטה זו כאן, ראוי לציין כי אם התוכנית תוכננה מתוך A הוא יורש את העדיפות שלו. scheduler.yield() user-visible Prioritized Task Scheduling API postTask() scheduler.yield() postTask() כיצד להשתמש scheduler.yield (). ברגע שאתה מבין איך הכל עובד – סוגי המשימות, הבעיה הנגרמת על ידי פעולות חסימה ארוכות, ואת העדיפויות, השימוש אבל יש להשתמש בו בחוכמה ובזהירות הנדרשת.הנה גרסה מעודכנת של function using עכשיו, במקום אתה רק צריך להתקשר והחלק הנותר נשאר ללא שינוי. scheduler.yield() heavyWork() scheduler.yield() setTimeout() await scheduler.yield() async function heavyWork() { // Yield to Main Thread to avoid UI blocking before heavy work await scheduler.yield() const data = Array.from({ length: 200 }, (_, i) => i) const result = [] // Interval at which execution will be yielded to the main thread (approx. ~ 25%). const yieldInterval = Math.ceil(data.length / 4) for (let i = 0; i < data.length; i++) { // Yield control to Main Thread to update UI and handle other tasks. if (i % yieldInterval === 0) { await scheduler.yield() } result.push(threadBlockingEnabled ? blockingTask(10) : data[i]) } return result } עכשיו, כאשר משתמש מתחיל a שימוש בפונקציה "ההבדל הוא ברור מאוד, קודם כל" " כפתור אינו עמיד, ושנית, המשתמש לוחץ על אירועים על " כפתורים מעובדים בהצלחה, אשר לא לחסום את האינטראקציה של המשתמש עם הדף. heavyWork() scheduler.yield() OK Log זאת אומרת, בתחילה, את הפונקציה הושקה, והכפתור הועבר מחדש מבלי להדביק.בזמן שהמשתמש ביצע את המשימה הקשה הזאת, הוא לחץ על " האירוע מעובד בהצלחה, והנתונים מודפסים לקונסולה. הפונקציה נמשכה, והתוצאה הסופית נרשמה לקונסולה.אחרי השלמת הפונקציה, המשתמש לחץ על " בקצרה, אתה יכול לתת לדפדפן שלך הפסקה עם רק שורה אחת. heavyWork() Log heavyWork() Log דמוי תיאור פונקציונליות עכשיו שאתה חוקר את התיאוריה, בואו נעבור לתרגול ולראות דומיין עובד אמיתי.זה יישום בנקאי סימולציה. כמובן, זה פיקטיבי ופשוט, אבל זה מציין מספיק מורכבות בעולם האמיתי כדי לעזור לך להבין איך לחסום את החוט הראשי משפיע על האינטראקטיביות, וכיצד יכול לעזור scheduler.yield() הנה מה שהמשתמש רואה בממשק: – By default, the account balance is hidden behind a placeholder of asterisks. This is a familiar pattern in real banking apps, where sensitive information is hidden unless explicitly revealed by the user. A button labeled " " toggles visibility. Balance section Show balance – A visual representation of a bank card, shown front side by default, where some details are displayed: card type in the top left corner, last 4 digits of the card, the cardholder's name, and payment system, at the bottom right corner of the card. There are two buttons to the right of the card: Bank card – which flips the card when clicked. The back side of the card reveals sensitive card data like its full number, expiration date, and CVV code. Although the card number is generally not considered private information, some applications still prefer not to show the full number by default, but only if the user initiates it. However, I know and even use banks that generally do not allow you to see the bank card number in the application. Show card details – by clicking this button, this feature supposedly generates a list of transactions on the card and displays them in the table below. It imitates the real functionality where users can generate reports on bank card transactions. In reality, these reports can be complex tables with many customizable filters and the ability to download the report as a file. Such operations might involve heavy computations, process a huge amount of data, making them resource-intensive and time-consuming. For the sake of the demo, it's simplified. Under the hood, the " " button triggers the previously discussed function, which simply blocks the main thread using the function, which was also discussed above. After that, static mock transaction data is simply rendered into the table. Generate report Generate report heavyWork() blockingTask() התנהגות היישום ניתן להתאים אישית באמצעות ההגדרות השונות על לוח ההגדרות בצד שמאל. הגיע הזמן להסביר מה הוא עושה: Main Thread blocking – קובע אם ה- Main Thread יהיה חסום, למעשה, כאשר אפשרות זו מופעלת, הפונקציה BlockingTask() מתבצעת. Scheduler.yield() – מדווח אם scheduler.yield() משמש. אורך מסדר נתונים – שולט בכמה אלמנטים מתחדשים על-ידי הפונקציה heavyWork(): כמה אלמנטים יותר, כך זה לוקח יותר זמן. אורך זמן בלוק – מציין כמה מילישניות לוקח לכל אלמנט של המסדרון לעיבוד. – Defines how often is called, as a percentage of progress through the array. That is, the lower this number, the more often it will be called. In earlier examples, we used a 200-element array with a 10ms delay and a 25% interval – a good balance for visible impact without excessive delay. With larger datasets, a smaller interval is often better. But, as always, it depends. Yield interval scheduler.yield() → הפגנה לאחר שהסדרו את כל הפונקציונליות וההגדרה, בואו נסתובב דרך תסריט שימוש אמיתי ולראות כיצד חסימת החוט הראשי משפיעה על חוויית המשתמש. מחסום ומחסום אנו גם להגדיל את אורך המסדרון קצת, כך הפעלה כבדה לוקח יותר זמן, נותן לנו זמן לצפות את ההשפעות. מאחורי הסצנות, זה מפעיל את הפונקציה, אשר מעבד 1000 אלמנטים, שבו כל אלמנט לוקח 10 מילישניות. Main Thread scheduler.yield() Generate report heavyWork() תראו מה קורה: The " כפתור נשאר תקוע, זה לא להדפיס, ואת המשתמש לא להציג מחדש. » אז » כפתורים, אבל שום דבר לא מגיב. הממשק קפוא לחלוטין, אין אנמיה, אין משוב, אין תחושה של התקדמות. זוהי דוגמה קלאסית של חווית משתמש גרועה. האפליקציה נראית קפואה, למרות שהיא טכנית עדיין עובדת. Generate report Show card details Show balance בואו נפתור את החסרונות הללו באמצעות כך נראית ההגדרה: כך נראית ההגדרה: הפעם, האפשרות להשתמש אורך המערך מוגדל מעט, רק למען הבהירות. זמן החסימה נשאר אותו הדבר, 10 מילישניות. תקופת התגובה מופחתת ל-5% על מנת להבטיח תגובה חלקה יותר, מכיוון שהאורך של המסדרון גדל. scheduler.yield() Main Thread scheduler.yield() scheduler.yield() ועכשיו עם הגדרת העדכון, אותו זרימת משתמשים נראה שונה לחלוטין. " כפתור נלחץ, זה מחזיר כראוי, ואת האנימציה לטעון מופיע. בעוד הדו"ח נוצר, המשתמש אינטראקציה בהצלחה עם המשתמש: הם יכולים לסובב את הכרטיס ולהחליף את האיזון. היישום נשאר תגובה, גם אם האנימציות הם מעט פחות רך, זה צעד ענק קדימה לעומת הקפאת הקודמת. זוהי חוויה הרבה יותר טובה. המשתמש מודע, בשליטה, ולא נותר לנחש אם האפליקציה עובדת. כמובן, ניתן לאופטימיזציה נוספת של היישום האמיתי, אבל גם בצורת פשוטה זו, ההבדל הוא מדהים. Generate report scheduler.yield() מסקנה אז, היום למדת על נותן את הדפדפן שלך הפסקה, את החשיבות של התמסרות כדי לבצע משימות עדיפות גבוהות יותר, ואת היתרונות והחסרונות של טכניקות אלה. אבל המטרה שלי הייתה לתת לך בסיס מוצק, מספיק כדי להתחיל להתנסות, מספיק כדי להתחיל לחשוב אחרת על איך הקוד שלך משחק עם הדפדפן. Main Thread Prioritized Task Scheduling API קישורים שימושיים דומיין מקוון – https://let-your-browser-take-a-breather.onrender.com/ Demo GitHub אחסון – https://github.com/WOLFRIEND/let_your_browser_take_a_breather תרשים – https://drive.google.com/file/d/1FLKKPaseyypE3pVXXn7Cj0aWac3rCayn/view