The bug is not always the failed request

A common product bug is a request that succeeds after the user has already moved on. Search results from an old query replace newer results. A modal closes but the save callback still updates state. A slow route response lands after a faster route response.

Cancellation is not only a performance concern. It is a correctness concern.

ts
const controller = new AbortController();

const response = await fetch("/api/search?q=react", {
  signal: controller.signal
});

controller.abort();

A timeout is a product decision

Timeouts should match the user expectation for the workflow. A typeahead search should fail quickly. A report export can take longer if the UI explains what is happening.

Use timeout wrappers to make the behavior explicit instead of letting a request hang indefinitely.

ts
function timeoutSignal(ms: number) {
  const controller = new AbortController();
  window.setTimeout(() => controller.abort(), ms);
  return controller.signal;
}

Try the idea

API error response demoValidation error
User message

Please enter a valid email address.

Developer log

422 VALIDATION: email format failed

Avoid request races in search and filters

When a user types quickly, every query should not be allowed to win. Cancel previous requests or keep a request version so only the latest response can update the UI.

Production notes for JavaScript apps

When this concept moves from a tutorial into a real app, the important question is not whether the example works once. It is whether the behavior is still predictable when network latency, retries, user interaction, rendering, and logging all happen together.

Keep the boundary visible. Name the queue, state, error, or ownership rule in the code so the next developer can understand why the behavior exists without reading the whole feature from top to bottom.

Conclusion

Good request handling is not only about catching errors. It is about making sure old work cannot overwrite the current user intent.