Tools: Build Live search Input with API fetching & cancelling using JavaScript

Tools: Build Live search Input with API fetching & cancelling using JavaScript

Source: Dev.to

1. Listen and Debounce input ## 2. Cancel previous fetch requests when a new search starts ## 3. Handle network errors gracefully 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: In this article we'll implement exactly this behavior using only vanilla JavaScript — no libraries, no frameworks, no build tools. Goals of the implementation Let begin step by step Add a HTML element to listen 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 Listen to the HTML element and perform debounce behavior and API call fetching 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) Perform API call and provide the AbortController's signal 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. 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**. We need to add try/catch logic as AbortError can help trigger the .catch() block Make sure to abort previous controller before creating new one. 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!!! Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK: <input type="text" id="searchInput" placeholder="Type to search..." /> Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: <input type="text" id="searchInput" placeholder="Type to search..." /> CODE_BLOCK: <input type="text" id="searchInput" placeholder="Type to search..." /> COMMAND_BLOCK: function debounce(fn, delayMs) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delayMs); }; } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: function debounce(fn, delayMs) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delayMs); }; } COMMAND_BLOCK: function debounce(fn, delayMs) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delayMs); }; } COMMAND_BLOCK: 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 COMMAND_BLOCK: const debouncedSearch = debounce(fetchData, 350); // ← adjust delay to taste // Event listener searchInput.addEventListener('input', (e) => { const query = e.target.value; debouncedSearch(query); }); COMMAND_BLOCK: const debouncedSearch = debounce(fetchData, 350); // ← adjust delay to taste // Event listener searchInput.addEventListener('input', (e) => { const query = e.target.value; debouncedSearch(query); }); CODE_BLOCK: let currentAbortController = null; // To track & abort previous requests currentAbortController = new AbortController(); const signal = currentAbortController.signal; Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: let currentAbortController = null; // To track & abort previous requests currentAbortController = new AbortController(); const signal = currentAbortController.signal; CODE_BLOCK: let currentAbortController = null; // To track & abort previous requests currentAbortController = new AbortController(); const signal = currentAbortController.signal; CODE_BLOCK: 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 CODE_BLOCK: const response = await fetch( `https://jsonplaceholder.typicode.com/posts?q=${encodeURIComponent( query )}`, { method: 'GET', mode: 'cors', // Explicit for CORS compatibility signal: signal, // For aborting } ); CODE_BLOCK: const response = await fetch( `https://jsonplaceholder.typicode.com/posts?q=${encodeURIComponent( query )}`, { method: 'GET', mode: 'cors', // Explicit for CORS compatibility signal: signal, // For aborting } ); CODE_BLOCK: // Abort previous request if exists if (currentAbortController) { currentAbortController.abort(); } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: // Abort previous request if exists if (currentAbortController) { currentAbortController.abort(); } CODE_BLOCK: // Abort previous request if exists if (currentAbortController) { currentAbortController.abort(); } CODE_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 CODE_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 } CODE_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 } CODE_BLOCK: // 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_BLOCK: // 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... } CODE_BLOCK: // 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... } - 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. - Listen and Debounce input (wait a short time after typing stops) - Cancel previous fetch requests when a new search starts - Handle network errors gracefully - 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).