A promise settles only once. loadProfile() .then(profile => renderProfile(profile)) .catch(error => showError(error.message)) .finally(() => hideSpinner());
3.3 |
| Mechanism | Lifetime and characteristics | Appropriate use |
|---|---|---|
| Cookies | Sent with matching HTTP requests; small; controlled by security attributes | Sessions and server-related state |
sessionStorage | Per-origin and per-tab; usually ends when the tab closes | Temporary form or navigation state |
localStorage | Per-origin; persists across browser sessions; string values only | Small non-sensitive preferences |
| IndexedDB | Asynchronous database with structured records | Larger offline data and application caches |
| Cache API | Stores request/response objects, often with service workers | Offline and performance caching |
localStorage.setItem("theme", "dark");
const theme = localStorage.getItem("theme") ?? "light";
localStorage.removeItem("theme");Storage operations can throw, for example when storage is blocked or unavailable. Values must be serialized:
localStorage.setItem("settings", JSON.stringify({ compact: true }));
const settings = JSON.parse(localStorage.getItem("settings") || "null");Do not store passwords, access tokens, or other sensitive secrets in localStorage; any script running in the origin can read them. Cookies used for sessions should generally use Secure, HttpOnly, and an appropriate SameSite policy. Storage is subject to origin boundaries and quota limits, and it is not a substitute for authoritative server-side data.
Constructing URLs by string concatenation is error-prone because escaping, existing query parameters, and fragments may be mishandled. The URL API provides structured manipulation.
const url = new URL("https://example.com/search");
url.searchParams.set("q", "web APIs");
url.searchParams.set("page", "2");
console.log(url.href);URLSearchParams automatically encodes parameter values. It can also read parameters from the current page:
const params = new URLSearchParams(window.location.search);
const query = params.get("q") || "";Use history.pushState() or replaceState() to update application URLs without a full page reload, while ensuring that the resulting URL remains meaningful and accessible. Never trust URL parameters as safe HTML or as authorization instructions; validate them and encode output according to context.
An external service may provide maps, payments, weather, authentication, analytics, or educational data. Integration requires more than writing a request. Developers must examine authentication, rate limits, service availability, data ownership, privacy terms, and failure behavior.
Browsers enforce the same-origin policy. A page may not freely read responses from another origin unless that origin permits access through Cross-Origin Resource Sharing (CORS). CORS is enforced by browsers; it is not an authentication mechanism. A public API key placed in frontend code should be regarded as visible. Secret credentials belong on a trusted server or backend proxy.
External data should be treated as untrusted input. Validate response structure, limit displayed content, use HTTPS, and avoid exposing personal information. If a service processes user data, obtain appropriate consent and follow applicable privacy and data-protection requirements.
A fictional college wants a course search page. Users enter a phrase, the page requests matching courses from /api/courses, displays results, and preserves the selected page size between visits.
The first prototype sends one request for every keystroke, displays no loading state, assumes all responses are successful, and allows an earlier slow response to replace newer results. It also stores a session token in localStorage.
The interface has concurrency, reliability, and security problems. Requests need debouncing and cancellation. HTTP status and JSON shape must be checked. The token must be removed from client storage and managed through a secure session design.
let activeController;
let searchTimer;
function scheduleSearch(term) {
clearTimeout(searchTimer);
searchTimer = setTimeout(() => searchCourses(term), 300);
}
async function searchCourses(term) {
activeController?.abort();
activeController = new AbortController();
setState("loading");
try {
const url = new URL("/api/courses", window.location.origin);
url.searchParams.set("q", term.trim());
const response = await fetch(url, { signal: activeController.signal });
await checkResponse(response);
const data = await response.json();
if (!Array.isArray(data.courses)) {
throw new Error("Unexpected response format");
}
renderCourses(data.courses);
setState(data.courses.length ? "success" : "empty");
} catch (error) {
if (error.name !== "AbortError") {
setState("error");
}
}
}The page may store a non-sensitive preference such as page size in localStorage, but authentication should use a safer server-managed design. Accessible status text should announce loading and errors, and the results region should be updated using safe text operations.
The revised design reduces unnecessary requests, prevents stale responses from winning, distinguishes network failures from empty results, and provides a more predictable user experience. The example also demonstrates that asynchronous correctness includes interface state, data validation, and security—not only promise syntax.
Ignoring rejected promises: Use try/catch, .catch(), or deliberate error propagation.
Assuming Fetch rejects on 404: Check response.ok or the status code.
Reading a response body twice: A response body is generally consumed once; clone it only when there is a justified need.
Serializing independent requests: Use Promise.all() where operations do not depend on one another.
Updating removed elements: Check component lifetime or cancel work when a view is destroyed.
Unbounded retries: Use a retry limit, delay, and preferably exponential backoff with jitter.
Leaking sensitive information: Avoid secrets in source code, URLs, logs, and web storage.
Blocking the main thread: Break up heavy computation or move suitable work to a Web Worker. Asynchronous network code alone does not make CPU-heavy code non-blocking.
Poor accessibility: Announce state changes, preserve keyboard operation, and do not communicate status by color alone.
Modern applications increasingly combine Fetch with service workers, the Cache API, WebSockets, Server-Sent Events, Web Workers, and framework data-loading systems. These technologies solve different problems: Fetch handles request-response operations; WebSockets support ongoing two-way communication; Server-Sent Events support server-to-client streams; workers move computation away from the main thread.
The underlying principles remain stable: make state explicit, validate boundaries, handle cancellation, protect credentials, and design for partial failure. Newer APIs such as AbortSignal.timeout() may simplify time limits where supported, but compatibility should be checked before production use.
Asynchronous JavaScript allows browsers to remain responsive while external operations complete. The event loop coordinates the call stack, browser APIs, task queue, and microtask queue; promise reactions are generally processed before the next task. Callbacks are foundational, promises compose asynchronous results, and async/await provides readable promise-based control flow.
Fetch requires explicit HTTP-status checking, structured JSON parsing, cancellation where appropriate, and careful management of loading, empty, success, and error states. localStorage, sessionStorage, cookies, IndexedDB, and the Cache API have different lifetimes and security properties. URL APIs safely construct and inspect query parameters. External services require attention to CORS, authentication, rate limits, privacy, validation, and failure.
Professional asynchronous code is not merely code that “waits.” It controls concurrency, communicates state to users, protects data, validates boundaries, and remains usable when networks and services fail.
Event loop: Runtime mechanism that schedules asynchronous callbacks for JavaScript execution.
Call stack: Structure containing currently executing JavaScript functions.
Task: A queued unit of work such as a timer or user-event callback.
Microtask: High-priority queued work such as a promise reaction.
Callback: Function executed in response to a later event or result.
Promise: Object representing a future fulfillment or rejection.
async function: Function that always returns a promise.
await: Expression that pauses an async function until a promise settles.
Fetch API: Browser interface for making network requests.
HTTP status: Numeric result indicating the outcome of an HTTP request.
JSON: Text-based format for exchanging structured data.
AbortController: Object used to cancel abortable asynchronous operations.
CORS: Browser-controlled mechanism allowing selected cross-origin requests.
Origin: Combination of scheme, host, and port used for web security boundaries.
localStorage: Persistent, origin-scoped string key-value storage.
sessionStorage: Temporary, origin- and tab-scoped string storage.
IndexedDB: Asynchronous browser database for structured records.
URLSearchParams: API for reading and constructing URL query parameters.
Debouncing: Delaying action until input has remained inactive for a specified period.
XSS: Injection of executable untrusted content into a web page.
response.ok?AbortController solve?async/await, including strengths and limitations.localStorage, sessionStorage, IndexedDB, and the Cache API.localStorage and inserts API-provided descriptions with innerHTML. Identify the risks and propose safer alternatives.Which work is generally processed before the next task when the JavaScript call stack is empty?
A. A promise reaction in the microtask queue
B. A later network request that has not completed
C. A CSS file download
D. A newly created DOM element
Correct Answer: A. A promise reaction in the microtask queue
What must a developer normally do after calling fetch() to detect an HTTP 404 or 500 response?
A. Check response.ok or response.status
B. Wait for setTimeout()
C. Call JSON.stringify()
D. Read localStorage
Correct Answer: A. Check response.ok or response.status
Which Promise method waits for all input promises and reports both fulfilled and rejected outcomes?
A. Promise.race()
B. Promise.any()
C. Promise.allSettled()
D. Promise.resolve()
Correct Answer: C. Promise.allSettled()
What does an async function return?
A. A DOM element
B. A promise
C. A timer identifier only
D. A JSON string
Correct Answer: B. A promise
Which browser mechanism is generally most appropriate for a small non-sensitive preference that should persist across sessions?
A. localStorage
B. The call stack
C. A microtask
D. AbortController
Correct Answer: A. localStorage
What is the main purpose of URLSearchParams?
A. To execute JavaScript in a URL
B. To construct and inspect query parameters
C. To store passwords securely
D. To replace the event loop
Correct Answer: B. To construct and inspect query parameters
Why should a search interface cancel or ignore earlier requests?
A. To ensure an older, slower response cannot overwrite newer results
B. To make JSON valid
C. To bypass CORS
D. To make HTTP status codes unnecessary
Correct Answer: A. To ensure an older, slower response cannot overwrite newer results
Which statement about CORS is correct?
A. It is a browser-enforced mechanism for permitted cross-origin reading
B. It encrypts passwords stored in localStorage
C. It guarantees that an API key is secret
D. It prevents all network failures
Correct Answer: A. It is a browser-enforced mechanism for permitted cross-origin reading
Which operation converts a JavaScript object into JSON text?
A. JSON.parse()
B. JSON.stringify()
C. response.ok
D. URLSearchParams.get()
Correct Answer: B. JSON.stringify()
Which practice reduces the risk of cross-site scripting when displaying untrusted plain text?
A. Insert it with innerHTML without checking
B. Place it in a script element
C. Use textContent for plain-text output
D. Save it as a cookie
Correct Answer: C. Use textContent for plain-text output