לדלג לתוכן

Debounce ו-Throttle בממשק - מתי ולמה

יש לכם שדה חיפוש שיורה בקשת רשת על כל הקלדה של אות, או מאזין לגלילה שרץ מאות פעמים תוך כמה שניות ומשתק את הדפדפן. שני התסמינים האלה נובעים מאותה בעיה: אירועים בדפדפן, כמו הקלדה, גלילה או שינוי גודל חלון, יכולים להיורות עשרות ומאות פעמים בשנייה, והרבה פעמים אין שום סיבה להריץ פונקציה כבדה על כל אחד מהם. שתי טכניקות פותרות את זה בדרכים שונות, ולמרות שהן נשמעות דומות, הן מיועדות למצבים אחרים לגמרי.

דיליי עד שהשקט מגיע - Debounce

דבאונס אומר: תחכה. כל פעם שהאירוע יורה, בטלו טיימר קודם ותתחילו טיימר חדש. הפונקציה בפועל תרוץ רק כשעובר פרק זמן קבוע בלי שום אירוע נוסף. הדוגמה הכי נפוצה היא חיפוש בזמן הקלדה. אתם לא רוצים לירות בקשת שרת על כל תו, אתם רוצים לחכות עד שהמשתמש הפסיק להקליד לרגע, ורק אז לשלוח את הבקשה:

function debounce(fn, delay) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

const onSearchInput = debounce((value) => {
  fetchResults(value);
}, 300);

כל הקלדה מבטלת את הטיימר הקודם ומתחילה חדש. רק כשהמשתמש עוצר לרגע, fetchResults בפועל רץ. התוצאה היא בקשת רשת אחת בסוף במקום עשרות בקשות במהלך ההקלדה.

הרצה בקצב קבוע - Throttle

תרוטל שונה בתכלית: הוא לא מחכה שהאירועים ייגמרו, הוא פשוט מבטיח שהפונקציה לא תרוץ יותר מפעם אחת בכל פרק זמן קבוע, גם אם האירוע ממשיך לירות בלי הפסקה. הדוגמה הקלאסית היא מאזין לגלילה או לשינוי גודל חלון. אתם כן רוצים תגובה שוטפת, אבל אין צורך להריץ חישוב מחדש על כל פיקסל שהמשתמש גולל:

function throttle(fn, interval) {
  let lastCall = 0;
  return function (...args) {
    const now = Date.now();
    if (now - lastCall >= interval) {
      lastCall = now;
      fn.apply(this, args);
    }
  };
}

window.addEventListener("scroll", throttle(() => {
  updateStickyHeader();
}, 200));

כאן updateStickyHeader תרוץ לכל היותר פעם ב-200 מילישניות, לא משנה כמה פעמים אירוע הגלילה יורה בפועל. השימוש השני הנפוץ בתרוטל הוא בכפתורים: מניעת לחיצות חוזרות על כפתור שליחה על ידי הגבלה של פעולה אחת בכל חלון זמן, כדי שלחיצה כפולה בטעות לא תשלח את אותה פעולה פעמיים.

איך יודעים איזו מהן צריך

השאלה שכדאי לשאול היא: מעניין אותי המצב הסופי, או שאני רוצה תגובה שוטפת לאורך כל הדרך. אם מעניין אתכם רק המצב אחרי שהמשתמש סיים לפעול, כמו חיפוש, ולידציה של שדה טופס, או שמירה אוטומטית של טיוטה, דבאונס הוא הבחירה הנכונה. אם אתם צריכים משוב רציף בזמן שהפעולה מתבצעת, כמו עדכון פס התקדמות בגלילה, מעקב אחרי תזוזת עכבר, או הגבלת קצב לחיצות, תרוטל הוא הכלי המתאים.

שווה לזכור גם שלא חייבים לכתוב את שתי הפונקציות האלה מאפס בכל פרויקט. הרבה ספריות עזר כוללות מימושים בדוקים שלהן, אבל חשוב להבין את ההיגיון שמאחוריהן כדי לדעת איזו מהן לבחור ברגע הנכון, ולא רק להעתיק קוד בלי להבין למה הוא עובד ככה.

בקורס צד לקוח יש התייחסות רחבה יותר לטיפול נכון באירועי דפדפן, שנותנת בסיס טוב להבין מתי טכניקות כאלה נכנסות לתמונה.

לסיכום

דבאונס ותרוטל פותרים בעיה דומה בשתי דרכים שונות: דבאונס מחכה לשקט ורץ פעם אחת בסוף, תרוטל מגביל את קצב הריצה לאורך כל הזמן. ברגע שמפנימים את ההבדל, קל הרבה יותר לזהות באיזה מהם להשתמש בכל מקרה.

אם נתקלתם במקרה גבולי ולא בטוחים אם צריך דבאונס או תרוטל, אשמח לשמוע ולדבר על זה.

הצטרפו לקהילה בדיסקורד