DEV Community

Kalyan P C
Kalyan P C

Posted on

Build Live search Input with API fetching & cancelling using JavaScript

Modern web applications often feature live search or autocomplete fields: as the user types, results appear almost instantly.
To provide a smooth experience we need to solve two important problems:

  • Don't send an HTTP request after every single keystroke.
  • When the user types quickly → cancel outdated requests so the UI doesn't show stale or wrong results.

In this article we'll implement exactly this behavior using only vanilla JavaScript — no libraries, no frameworks, no build tools.

Goals of the implementation

  1. Listen and Debounce input (wait a short time after typing stops)
  2. Cancel previous fetch requests when a new search starts
  3. Handle network errors gracefully

Let begin step by step

1. Listen and Debounce input

Add a HTML element to listen

<input type="text" id="searchInput" placeholder="Type to search..." />
Enter fullscreen mode Exit fullscreen mode

Lets create debounce functionality. Debounce waits a certain amount of time after the last keystroke.To create a debounce functionality we can create a timer when input is received and clear the old timer

function debounce(fn, delayMs) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delayMs);
  };
}
Enter fullscreen mode Exit fullscreen mode

Listen to the HTML element and perform debounce behavior and API call fetching

const debouncedSearch = debounce(fetchData, 350); // ← adjust delay to taste

// Event listener
searchInput.addEventListener('input', (e) => {
  const query = e.target.value;
  debouncedSearch(query);
});
Enter fullscreen mode Exit fullscreen mode

2. Cancel previous fetch requests when a new search starts

To achieve the behavior of cancelling the previous request, we will make use of AbortController. It helps in signaling the fetch request to cancel.

Create a new AbortController which has a property called signal (which is an instance of AbortSignal)

let currentAbortController = null; // To track & abort previous requests


currentAbortController = new AbortController();
const signal = currentAbortController.signal;
Enter fullscreen mode Exit fullscreen mode

Perform API call and provide the AbortController's signal

const response = await fetch(
      `https://jsonplaceholder.typicode.com/posts?q=${encodeURIComponent(
        query
      )}`,
      {
        method: 'GET',
        mode: 'cors', // Explicit for CORS compatibility
        signal: signal, // For aborting
      }
    );
Enter fullscreen mode Exit fullscreen mode

To cancel the API call when new input is provided we need to call AbortController's abort method.

When you call .abort(): The signal.aborted boolean switches from false to true. An abort event is fired on the signal object.

// Abort previous request if exists
  if (currentAbortController) {
    currentAbortController.abort();
  }
Enter fullscreen mode Exit fullscreen mode

Then the browser "Unplugs" the request
If you passed that signal to a fetch() call, the browser is "listening" to that signal. As soon as the signal changes:

  • The HTTP Connection: The browser immediately stops the data transfer. If the request was still uploading or downloading bytes, the browser closes the TCP socket or stops the stream.

  • Network Activity: If you look at the Network Tab in your browser's Developer Tools, you will see the status of the request change to (canceled).

In your JavaScript code, the fetch() promise (which was "pending") is immediately rejected.

It throws a specific error: DOMException with the name **AbortError**.

3. Handle network errors gracefully

We need to add try/catch logic as AbortError can help trigger the .catch() block

try {
    // fetch data logic ....

    // handle http errors
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }

    const data = await response.json();
    // handle logic using data ....
  } catch (error) {
    if (error.name !== 'AbortError') {
      // Ignore abort errors
    }
  } finally {
    currentAbortController = null; // Reset after completion
  }
Enter fullscreen mode Exit fullscreen mode

Make sure to abort previous controller before creating new one.

// Function to make API call
async function fetchData(query) {
  // Abort previous request if exists
  if (currentAbortController) {
    currentAbortController.abort();
  }

  // Create new AbortController for this request
  currentAbortController = new AbortController();
  const signal = currentAbortController.signal;

  // fetch data logic...
}
Enter fullscreen mode Exit fullscreen mode

Code Playground

Using this approach only the last meaningful request completes and updates the UI. No dependencies, no build step, works in every modern browser.

Thanks for reading. Happy coding!!!

Top comments (0)