AbortController, and the search that races itself

A type-ahead fires a request per keystroke and the responses arrive out of order, so the results for a shorter query can overwrite the results for a longer one.

let controller;

async function search(term) {
  controller?.abort();
  controller = new AbortController();

  try {
    const res = await fetch(`/search?q=${encodeURIComponent(term)}`,
      { signal: controller.signal });
    render(await res.json());
  } catch (e) {
    if (e.name !== 'AbortError') throw e;
  }
}

Distinguishing the abort error from a real failure is required, or every cancellation is reported as a bug. The signal can be passed to several fetches to cancel them together, which is the neat part. It does not stop the server doing the work — the request has been sent — so this is about the client’s state rather than about saving anyone’s resources.