Language and browser services
Promises, functions and objects belong to the language; DOM, events, fetch and AbortController are browser host services used in this lesson. A valid JavaScript function can depend on APIs another host does not provide. Declare the context before interpreting an example, and do not infer support in every browser merely from publication of an ECMAScript edition.
An HTTP request has several observable boundaries. Fetch produces a Response when the accessible initial response arrives; that does not mean the body has already been fully read or the data is valid for the application. Network rejection, an HTTP error status, malformed JSON and a missing required field are different outcomes. Preserve these distinctions in diagnosis even if the interface shows a shared short message.
Listener and event identity
On a button with a descendant icon, clicking the icon can make that descendant the target while currentTarget identifies the button whose listener is executing. This distinction prevents acting on the wrong element. currentTarget has its listener meaning only during execution; if the reference is needed later, store the element in a variable while the handler is active. Retaining the event object does not freeze all its transient properties.
To remove a listener, retain function identity and use the same event type and capture value. A new arrow with the same body is still another function. A capture true registration is not removed by the false variant. Removal does not require repeating every other option, such as passive; distinguish initial configuration from the matching criteria used during removal.
Default action and text presentation
preventDefault addresses the default action of a cancelable event when the listener is not passive. It neither removes the listener nor, by itself, stops propagation to other listeners. A navigation or submission problem is not automatically solved by a propagation rule, and duplicate listeners are not fixed by canceling the default action. First identify which effect is occurring.
To display an external label as literal text, textContent avoids interpreting its string as HTML markup. Assigning "<b>Ready</b>" should show those characters when that is the contract. This assignment replaces the previous children of the element, so choose the target carefully. It is not a universal mechanism for validating URLs, business rules or other output contexts; it performs the bounded task of displaying text.
HTTP status, body and data contract
An ordinary 404 response does not automatically reject the fetch promise. Check response.ok or the specific status allowed by the contract. response.ok covers success statuses 200–299; it does not establish that the body contains required fields. response.json() is another asynchronous operation: reading or syntax can fail, and successful parsing can still produce null, an array or another value the application does not accept.
The body is a consumable stream. After successfully reading a non-null JSON body, calling json() again on the same Response does not provide a cache of the object. Retain the parsed value or, if independent consumers are needed, prepare a clone before consumption. Also avoid treating no-cors as a way to read a blocked response: an opaque response does not expose the body and headers this example needs to the script.
Cancel and decide whether an outcome is still current
AbortController requests cancellation from operations receiving its signal. The request can fail fetch or body reading even after the initial Response has arrived. Without a custom reason, ordinary cancellation uses AbortError. This is a local observation: it proves neither that the server never started work nor that it undid an accepted write. If a creation outcome is unknown, use the service identity and lookup contract before deciding to retry.
In a search, canceling an old query can reduce wasted work, but publication also needs an identity guard. Give each query an increasing generation and compare it with the current one immediately before changing the interface. The response arriving last can be old. Apply the same guard to error paths: an old failure must not replace current results with an unavailable message either.
Guided example: a current label
createLabelLoader creates private state for one element. Each load increments generation, requests cancellation of the previous request and stores a new controller. mine retains the generation of this call. HTTP and schema checks precede label publication. The contract accepts a string up to 200 UTF-16 code units, including an empty string; it is not counting visual characters. textContent displays the label literally.
Trace A with mine 1 and B with mine 2. Once B has started, A cannot publish on either success or catch because the current generation is 2. The finally block for A must not clear active either: that reference can already belong to B. The finally guard preserves the ability of a later call to cancel B. There is no await between the final generation comparison and text assignment in the stated ordinary DOM context.
Example limits and useful diagnosis
The example is a display adapter for authorized GETs, not a generic function returning business data. Its catch converts a current failure into "Unavailable"; the load promise does not certify successful data retrieval. In an application needing failure classification, retain explicit state or suitable internal diagnostics. The short label alone does not distinguish network, HTTP, JSON and schema failures.
Before changing code, reconstruct the sequence: selected query, generation, request start, headers, body, validation and publication decision. Do not introduce fixed delays to hide a race. If remote writes are involved, the label example provides neither idempotency nor rollback; use the contract of the specific service. Questions assess these decisions through excerpts and scenarios without requiring browser execution.
function createLabelLoader(label) {
let generation = 0;
let active = null;
return async function load(url) {
const mine = ++generation;
if (active !== null) active.abort();
const controller = new AbortController();
active = controller;
label.textContent = "Loading";
try {
const response = await fetch(url, { signal: controller.signal });
if (!response.ok) throw new Error("HTTP " + response.status);
const data = await response.json();
if (data === null || typeof data !== "object" || Array.isArray(data) ||
typeof data.label !== "string" || data.label.length > 200) {
throw new TypeError("label schema");
}
if (mine !== generation) return;
label.textContent = data.label;
} catch (error) {
if (mine !== generation) return;
label.textContent = "Unavailable";
} finally {
if (mine === generation) active = null;
}
};
}The label element is an existing ordinary DOM element intended only for text. URLs are authorized application read-only GETs. The code uses standard browser APIs. Consider consecutive calls A and B whose responses may finish out of order; trace mine, generation and active.
Common pitfalls
Removing listeners with a different function; assuming preventDefault stops propagation; reading a body twice; using innerHTML for external text; treating abort as confirmation of no effects.
Related topics: Errors, validation and failure contracts · JSON, text and time contracts · Promises, async and error propagation · State, ordering and integration diagnosis
Each boundary has its own outcome. Validate both response data and identity before publication; local cancellation alone does not describe remote state.
Reference: Using Fetch · ECMAScript 2026 (17th edition); browser APIs identified separately