Verwendung von AbortController als Alternative zum Entfernen von Event Listeners

Avatar of Carter Li
Carter Li am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Die Idee eines „abbrechbaren“ Fetch kam 2017 auf, als AbortController veröffentlicht wurde. Dies gibt uns die Möglichkeit, eine API-Anfrage, die von fetch() initiiert wurde – sogar mehrere Aufrufe – jederzeit abzubrechen.

Hier ist ein super einfaches Beispiel, das AbortController verwendet, um eine fetch()-Anfrage abzubrechen

const controller = new AbortController();
const res = fetch('/', { signal: controller.signal });
controller.abort();
console.log(res); // => Promise(rejected): "DOMException: The user aborted a request"

Seinen Wert sieht man wirklich, wenn er für eine moderne Schnittstelle von setTimeout verwendet wird. Auf diese Weise ist es ziemlich einfach, ein Fetch mit einem Timeout von sagen wir 10 Sekunden zu versehen.

function timeout(duration, signal) {
  return new Promise((resolve, reject) => {
    const handle = setTimeout(resolve, duration);
    signal?.addEventListener('abort', e => {
      clearTimeout(handle);
      reject(new Error('aborted'));
    });
  });
}

// Usage
const controller = new AbortController();
const promise = timeout(10000, controller.signal);
controller.abort();
console.log(promise); // => Promise(rejected): "Error: aborted"

Aber die große Neuigkeit ist, dass addEventListener seit Chrome 88 ein Abort Signal akzeptiert. Was ist daran cool? Es kann als Alternative zu removeEventListener verwendet werden.

const controller = new AbortController();
eventTarget.addEventListener('event-type', handler, { signal: controller.signal });
controller.abort();

Was ist *noch* cooler? Nun, da AbortController in der Lage ist, mehrere abbrechbare Anfragen auf einmal abzubrechen, strafft es den Prozess des Entfernens mehrerer Listener auf einmal. Ich habe es bereits für Drag-and-Drop als besonders nützlich empfunden.

Hier ist, wie ich ein Drag-and-Drop-Skript ohne AbortController geschrieben hätte, wobei zwei removeEventListener-Instanzen zum Löschen zweier verschiedener Ereignisse verwendet werden.

// With removeEventListener
el.addEventListener('mousedown', e => {
  if (e.buttons !== 1) return;

  const onMousemove = e => {
    if (e.buttons !== 1) return;
    /* work */
  }

  const onMouseup = e => {
    if (e.buttons & 1) return;
    window.removeEventListener('mousemove', onMousemove);
    window.removeEventListener('mouseup', onMouseup);
  }

  window.addEventListener('mousemove', onMousemove);
  window.addEventListener('mouseup', onMouseup); // Can’t use `once: true` here because we want to remove the event only when primary button is up
});

Mit dem neuesten Update akzeptiert addEventListener die Eigenschaft signal als zweites Argument, wodurch wir abort() einmal aufrufen können, um alle Event-Listener zu stoppen, wenn sie nicht mehr benötigt werden.

// With AbortController
el.addEventListener('mousedown', e => {
  if (e.buttons !== 1) return;

  const controller = new AbortController();

  window.addEventListener('mousemove', e => {
    if (e.buttons !== 1) return;
    /* work */
  }, { signal: controller.signal });

  window.addEventListener('mouseup', e => {
    if (e.buttons & 1) return;
    controller.abort();
  }, { signal: controller.signal });
});

Auch hier ist Chrome 88 derzeit der einzige Ort, an dem addEventListener offiziell ein AbortSignal akzeptiert. Während andere große Browser, einschließlich Firefox und Safari, AbortController unterstützen, ist die Integration seines Signals mit addEventListener derzeit nicht möglich… und es gibt keine Signale (Wortspiel beabsichtigt), dass sie daran arbeiten werden. Dennoch ist ein Polyfill verfügbar.