API & webhooks
Display REST API Data in JavaScript Without Stale Results
Render a fetched list safely, keep loading and errors visible, and prevent an older API response from overwriting a newer selection.
Give each request ownership of its displayed result
When an interface fetches data after a selection changes, responses can arrive in a different order from the requests. A slow request for the first selection must not overwrite a faster result for the second selection. Handle that ordering deliberately instead of assuming the latest response always belongs to the latest user action.
This example expects a response shaped as an object whose items property is an array of strings. The caller supplies a list element and a separate status element. Give the status element an appropriate live status role in the page so loading and completion messages are available to assistive technology.
Combine cancellation with an explicit request sequence
function createNoteLoader(list, status, fetcher = fetch) {
let sequence = 0;
let active = null;
return async function load(url) {
const id = ++sequence;
active?.abort();
const controller = new AbortController();
active = controller;
list.replaceChildren();
status.textContent = "Loading notes…";
try {
const response = await fetcher(url, {signal: controller.signal});
if (!response.ok) {
await response.body?.cancel();
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
if (!data || !Array.isArray(data.items)
|| !data.items.every(item => typeof item === "string")) {
throw new Error("Expected an items array of strings.");
}
if (id !== sequence) return;
const nodes = data.items.map(title => {
const item = document.createElement("li");
item.textContent = title;
return item;
});
list.replaceChildren(...nodes);
status.textContent = nodes.length ? "Notes loaded." : "No notes found.";
} catch (error) {
if (id !== sequence) return;
status.textContent = error.name === "AbortError"
? "Request cancelled." : "Could not load notes: " + error.message;
}
};
}
Starting another load aborts the previous controller and advances the sequence. The sequence check also protects the display when a test double or another asynchronous stage does not honor cancellation immediately. Both success and failure paths check ownership before publishing a message.
The function clears the previous list when a new load starts. That is a deliberate interface policy: the empty area with a loading message represents the new request. An application that retains old data while refreshing should label it as previous data and keep that state distinct from the eventual result.
Treat returned strings as text
Each title is assigned through textContent. A value that looks like an HTML image or script remains visible text in the list rather than becoming an executable element. The textContent reference describes this text-oriented DOM property.
Do not replace this assignment with innerHTML simply to shorten the example. If the application intentionally supports formatted content, it needs a separate, carefully defined rendering and sanitization policy. A normal note title does not need arbitrary HTML interpretation.
Keep empty data and failures distinguishable
An empty items array is a successful response with zero notes, so it produces No notes found. A malformed response or HTTP failure produces an error message. These are different states; returning an empty array from every catch block hides operational failures as if a search merely had no matches.
The example handles a bounded list appropriate to a small interface. Very large results need pagination or incremental rendering rather than spreading thousands of nodes into one update. The server's response-size and pagination contract should guide the client design.
Handle navigation as well as input changes
AbortController supplies the cancellation signal used by fetch. In a component framework, also connect cancellation and ownership invalidation to unmounting or route changes. This small loader shows replacement requests; it is not a complete component lifecycle manager.
Keep the selected URL or identifier in the same state model that starts the load. Avoid reading a mutable form field again when a response arrives, because that field may now describe a different request. The response should be interpreted according to the inputs that produced it.
Force the responses to arrive out of order
In a controlled test, start a delayed first request and then a fast second request. Complete the second, then release the first, and assert that only the second list remains. Repeat with the older request failing after the newer one succeeds; its error must not replace the newer success message.
Also test an empty list, an HTML-looking title and a malformed items value. Use the REST API Testing Tool to inspect a permitted endpoint's raw response, while keeping these ordering and rendering checks in the actual interface test.
Check keyboard interaction at the control that changes the selection. A correct response-order test does not prove that the user can reach that control, understand its current value or hear the loading message. Verify those behaviors on the page that integrates the loader.


