Use the Fetch API to load JSON with proper error handling, and cancel an in-flight request with AbortController (e.g. a timeout or a superseded search).
Whether you check response.ok, parse safely, and can abort a request.
fetch(url, options) returns a promise for a Response. A key gotcha: fetch only REJECTS on network failure — a 404 or 500 still RESOLVES, so you must check response.ok (or response.status) yourself and throw otherwise. Read the body with an async method matching the content: response.json(), .text(), .blob() (each returns a promise and can only be read once). Cancellation uses AbortController: create one, pass controller.signal in the options, and call controller.abort() to cancel — the fetch promise rejects with a DOMException whose name is 'AbortError', which you special-case in catch. This powers two common patterns: a TIMEOUT (abort after N ms; or use AbortSignal.timeout(ms)), and SUPERSEDING — in a search-as-you-type box, abort the previous request when a new keystroke fires so a slow earlier response can't overwrite a newer one (a race fix). Always clear the timeout in finally. For parallel requests use Promise.all; for the first to settle, Promise.race. Add credentials:'include' to send cookies cross-origin (subject to CORS).
Data fetching with cancellation: request timeouts, and aborting stale search/autocomplete requests on new input.
// (Sandboxed here: offline fetch fails, so this is read-only reference.)
async function getJSON(url, ms) {
const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), ms); // cancel if too slow
try {
const res = await fetch(url, { signal: ctrl.signal });
if (!res.ok) throw new Error('HTTP ' + res.status); // fetch does NOT throw on 404/500
return await res.json();
} catch (err) {
if (err.name === 'AbortError') console.log('aborted (timeout)');
else console.log('failed:', err.message);
throw err;
} finally {
clearTimeout(timer);
}
}
getJSON('/api/user', 5000).then(u => console.log(u)).catch(() => {});