JavaScript
AJAX
Asynchronous Programming
Web Development
Performance Optimization

Ignoring old multiple asynchronous ajax requests

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

When users type quickly or trigger repeated actions, older Ajax requests may finish after newer ones and overwrite fresh UI state. This is one of the most common causes of stale search results and flickering interfaces. The solution is not only speed, but request coordination with cancellation or response ordering guards.

Core Topic Sections

Why stale response overwrites happen

Ajax calls are asynchronous and completion order is not guaranteed. If request A starts first and request B starts second, B may still finish earlier or later depending on network and server timing.

Without control logic, any late response can update the UI and show outdated data.

Typical high-risk flows:

  1. Search-as-you-type inputs.
  2. Filter controls with rapid toggles.
  3. Auto-refresh dashboards.

Strategy 1: sequence token guard

A simple and robust pattern is to track a monotonically increasing request id and ignore results from older ids.

javascript
1let latestRequestId = 0;
2
3async function search(query) {
4  const requestId = ++latestRequestId;
5
6  const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
7  const data = await res.json();
8
9  if (requestId !== latestRequestId) {
10    return; // stale response, ignore
11  }
12
13  renderResults(data);
14}

This works even if cancellation is unavailable.

Strategy 2: cancel previous request with AbortController

Modern fetch supports cancellation via AbortController.

javascript
1let activeController = null;
2
3async function search(query) {
4  if (activeController) {
5    activeController.abort();
6  }
7
8  activeController = new AbortController();
9
10  try {
11    const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`, {
12      signal: activeController.signal,
13    });
14    const data = await res.json();
15    renderResults(data);
16  } catch (err) {
17    if (err.name === 'AbortError') {
18      return;
19    }
20    console.error(err);
21  }
22}

Cancellation reduces wasted server and client work and prevents stale updates.

Strategy 3: debounce request creation

Debounce limits how often requests are sent during fast input changes.

javascript
1function debounce(fn, delayMs) {
2  let timer = null;
3  return (...args) => {
4    clearTimeout(timer);
5    timer = setTimeout(() => fn(...args), delayMs);
6  };
7}
8
9const debouncedSearch = debounce((q) => search(q), 250);
10
11document.querySelector('#search').addEventListener('input', (e) => {
12  debouncedSearch(e.target.value);
13});

Debounce complements cancellation, it does not replace it.

Combine methods for resilient UI

In production apps, the strongest pattern is:

  1. Debounce to reduce request volume.
  2. Abort previous request to stop wasted work.
  3. Sequence guard to block any stale completion edge case.

Layered defenses handle browser quirks, proxy retries, and backend variability better than one mechanism alone.

jQuery Ajax pattern for legacy code

Older codebases still using jQuery can cancel previous requests similarly.

javascript
1let currentXhr = null;
2
3function searchLegacy(query) {
4  if (currentXhr) {
5    currentXhr.abort();
6  }
7
8  currentXhr = $.ajax({
9    url: '/api/search',
10    method: 'GET',
11    data: { q: query },
12    success: (data) => renderResults(data),
13    error: (xhr, status) => {
14      if (status === 'abort') return;
15      console.error('request failed');
16    }
17  });
18}

This keeps legacy UI behavior predictable without full framework migration.

Backend considerations

Client-side cancellation does not always stop already-started backend work. For expensive queries, backend should also support:

  1. Fast fail for abandoned requests if framework supports cancellation tokens.
  2. Caching for repeated query prefixes.
  3. Rate limits per client to avoid resource spikes.

Coordinated client and server controls improve responsiveness under load.

Testing stale-request behavior

Test this explicitly with simulated latency:

  1. Delay older request response intentionally.
  2. Send newer request and verify only newest data renders.
  3. Ensure loading indicators clear correctly after abort.

Race-condition bugs are easy to miss without deterministic tests.

Common Pitfalls

  • Assuming responses return in request order and updating UI unconditionally.
  • Debouncing input but still allowing stale in-flight responses to overwrite UI.
  • Treating aborted request errors as real failures in user-facing logs.
  • Forgetting to clear loading state when a request is aborted.
  • Ignoring server-side cost of abandoned high-frequency requests.

Summary

  • Multiple asynchronous requests can produce stale UI without coordination.
  • Use request id guards, cancellation, and debounce together.
  • 'AbortController is the modern standard for fetch cancellation.'
  • Legacy jQuery flows can still enforce request replacement safely.
  • Race-condition testing is essential for reliable, real-time interfaces.

Course illustration
Course illustration

All Rights Reserved.