AbortController ב-JavaScript: ביטול בקשות אסינכרוניות בקלות

סער טויטו

סער טויטו

Software & Web Development


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

מה זה AbortController

AbortController הוא אובייקט מובנה ב-JavaScript שמספק לנו דרך פשוטה וסטנדרטית לבטל פעולות אסינכרוניות. הוא הוכנס כחלק מ-DOM API, ומאפשר לנו לבטל בקשות רשת (כמו fetch) או פעולות אסינכרוניות אחרות בצורה אלגנטית ונקייה.

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

AbortController ו-AbortSignal: איך הם עובדים יחד

AbortController מורכב משני חלקים עיקריים שעובדים יחד:

  • AbortController

    AbortController הוא האובייקט המרכזי המשמש ליצירת "בקר ביטול" (cancellation controller). הוא מספק את המתודה ()abort שמאפשרת לשלוח אות ביטול (signal) לכל פעולה אסינכרונית שמאזינה לו, ולסמן שיש להפסיק את הביצוע.

  • AbortSignal

    AbortSignal הוא האובייקט שמתקבל מתוך ה-AbortController באמצעות controller.signal. את ה-signal הזה מעבירים לפונקציות שתומכות בביטול (כמו fetch), והוא משמש כתעלת תקשורת שמעדכנת את הפונקציה אם התרחש ביטול.

// Create a new instance of AbortController
const controller = new AbortController();

// Access its signal
const signal = controller.signal;

// Use the signal in a fetch request
fetch('https://api.example.com/data', { signal })
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(err => {
    if (err.name === 'AbortError') {
      console.log('הבקשה בוטלה!');
    } else {
      console.error('שגיאה אחרת:', err);
    }
  });

// Trigger the abort
controller.()abort;

איך AbortController עובד מאחורי הקלעים

מנגנון הביטול של AbortController פועל באמצעות מערכת האירועים של JavaScript. הנה מה שקורה מאחורי הקלעים:

  • אובייקט ה-Signal הוא EventTarget

    אובייקט ה-AbortSignal הוא למעשה instance של EventTarget, כלומר הוא מסוגל לשדר אירועים (כמו 'abort') ולתמוך בהאזנה לאירועים, בדומה לאובייקטים ב-DOM. תכונה זו מאפשרת לפונקציות אסינכרוניות להירשם לאירועי ביטול ולטפל בהם בצורה מסודרת.

  • שידור אירוע ה-abort

    כאשר קוראים למתודה controller.()abort, מתבצעות מספר פעולות:

    • הפרופרטי signal.aborted מתעדכנת ל-true
    • אירוע בשם abort משודר על אובייקט ה-signal
    • הפרופרטי signal.reason מתעדכן עם ערך שמתאר את סיבת הביטול (אם סופקה)
  • האזנה לאירועים

    פונקציות כמו fetch רושמות מאזין אירועים (event listener) לאירוע ה-abort של ה-signal, ומבטלות את הפעולה שלהן כאשר האירוע מתקבל.

// Simplified demonstration of the internal abort signaling mechanism in fetch
function internalFetch(url, { signal } = {}) {
  // Check if the external signal is already aborted
  if (signal?.aborted) {
    return Promise.reject(new DOMException('הבקשה בוטלה', 'AbortError'));
  }
  
   // Create a new internal AbortController for the actual fetch
  const controller = new AbortController();
  const internalSignal = controller.signal;
  
  // Listen for the abort event on the external signal
  if (signal) {
    signal.addEventListener('abort', () => {
       // Propagate the abort to the internal controller
      controller.abort(signal.reason);
    });
  }
  
  // Execute fetch with the internal signal
  return fetch(url, { signal: internalSignal });
}

שימושים נפוצים ל-AbortController

  • ביטול בקשות fetch

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

  • הגדרת timeout לבקשות

    אפשר להשתמש ב-AbortController כדי להגדיר (timeout) לבקשות, כך שאם הבקשה לוקחת יותר מדי זמן, היא תבוטל אוטומטית.

    function fetchWithTimeout(url, options = {}, timeout = 5000) {
      const controller = new AbortController();
      const { signal } = controller;

      const timeoutId = setTimeout(() => {
        controller.()abort;
      }, timeout);

      return fetch(url, { ...options, signal })
        .finally(() => clearTimeout(timeoutId));
    }
  • ביטול מספר בקשות במקביל

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

    const controller = new AbortController();
    const { signal } = controller;

    // מספר בקשות שמשתמשות באותו signal
    const fetchUsers = fetch('/api/users', { signal });
    const fetchProducts = fetch('/api/products', { signal });

    // ביטול כל הבקשות בפעולה אחת
    controller.()abort;
  • ביטול האזנה לאירועים ב-DOM

    בדפדפנים שתומכים בכך, ניתן להשתמש ב-AbortSignal גם לביטול מאזינים לאירועים – לא רק בקריאות רשת.

    const controller = new AbortController();
    const { signal } = controller;

    // Add event listener with signal
    document.addEventListener('click', event => {
      console.log('Click event:', event);
    }, { signal });

    // Cancel the event listener
    controller.()abort;

התפתחויות חדשות

בגרסאות חדשות יותר של JavaScript, נוספו מספר תכונות ומתודות שהופכות את העבודה עם AbortController לנוחה אף יותר:

  • AbortSignal.timeout()

    מתודה סטטית חדשה שמאפשרת ליצור signal שיבוטל אוטומטית אחרי זמן מוגדר, ללא צורך ליצור controller נפרד.

    // יצירת signal שיבוטל אחרי 5 שניות
    const timeoutSignal = AbortSignal.timeout(5000);

    fetch('https://api.example.com/data', { signal: timeoutSignal });
  • סיבת ביטול (reason)

    בגרסאות חדשות יותר, אפשר להעביר סיבה לפעולת ה-abort, שתהיה זמינה דרך התכונה signal.reason:

    const controller = new AbortController();

    // ביטול עם סיבה
    controller.abort(new Error('המשתמש ביטל את הפעולה'));

    // גישה לסיבה
    console.log(controller.signal.reason);

סיכום

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

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

בעוד ש-fetch API היה הממשק הראשון שאימץ את AbortController, אנו רואים כעת תמיכה הולכת וגדלה בממשקים נוספים, מה שהופך את הידע שרכשתם במאמר זה לשימושי עוד יותר בעתיד.