Working with an API: failure, retries and races
What to do when a request is slow, fails, or arrives out of order.
16 min read
The happy path of a request takes ten minutes to write. Everything in this lesson is the rest of it.
fetch does not throw on 404 or 500. It rejects only when the request itself could not be made, which means a network failure. A 500 response resolves normally with ok: false, so code that only uses try/catch treats a server error as a success and then fails while reading fields that are not there. Check res.ok before touching the body.
Not every failure should be retried. A 500 or a network error may succeed on a second attempt. A 400 or a 404 will not, and retrying it wastes time for the person waiting and load on the server. Where you do retry, wait longer between attempts rather than immediately, and give up after a small number.
Requests arrive out of order. A user types "re", then "react". Two requests are in flight; the slower one may answer last. Without protection the results for "re" overwrite the results for "react", and the interface shows the wrong list with complete confidence. Either cancel the earlier request with an AbortController, or ignore any response that is no longer the most recent one. This is the defect that survives review most often, because it never appears on a fast connection.
Debounce the input, not the request. Waiting a few hundred milliseconds after the last keystroke before asking at all removes most of the traffic and most of the races with it.
Loading state has two lengths. A response in 100ms needs no spinner; showing one produces a flash that reads as a glitch. A response in three seconds needs one, or the user assumes nothing happened. Delaying the spinner by around 300ms covers both.
Go deeper
Lessons are not assessed. Progress towards the certificate is recorded from the work you submit. See all requirements