# Programatium Workshop — full text > Programatium Workshop is an independent programming-tutorial publication focused on browser JavaScript and practical debugging. It is not a software vendor, code-hosting service, standards body, training provider, certification program, or continuation of any former entity associated with this domain. # Cancel Fetch Requests with AbortController > Cancel a fetch request by creating an `AbortController` and passing its `signal` to `fetch()`, then calling `controller.abort()` when the user cancels, a newer request supersedes it, a timeout expires, or the owning view is removed. Create a new controller for each operation because an aborted signal stays aborted. In `catch`, identify cancellation from the signal you own rather than treating every network error as an abort. Clear timeout handles and interface state in `finally`. Client cancellation does not guarantee that a server stopped work or reversed a completed side effect. Source: https://programatium.com/cancel-fetch-abortcontroller/ Publisher: Programatium Workshop Published: 2026-07-19 Updated: 2026-08-26 ## Pass an abort signal into `fetch` In browser JavaScript, `fetch()` does not return a cancel method. Cancellation travels through an `AbortSignal`, usually created by an `AbortController`. ```js const controller = new AbortController(); const responsePromise = fetch("/api/search?q=otter", { signal: controller.signal, }); controller.abort(); ``` Calling `abort()` marks the signal as aborted and causes the fetch operation to reject if it has not already completed. According to [MDN's AbortController reference](https://developer.mozilla.org/docs/Web/API/AbortController), cancellation can also interrupt consumption of a response body or stream. One controller may signal several operations, but it is single-use: once aborted, its signal stays aborted. Create a fresh controller for a new request. Reusing the old one produces an immediate rejection, which is correct behavior and a surprisingly efficient way to make a search box appear haunted. ## Build a cancelable JSON helper Let the caller own cancellation by accepting a signal: ```js async function fetchJson(url, { signal } = {}) { const response = await fetch(url, { signal }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } return response.json(); } ``` `fetch()` resolves after an HTTP response even when its status is 404 or 500, so check `response.ok` or the status explicitly. Network failure, cancellation, HTTP failure, and invalid JSON are different outcomes. Do not turn all of them into “No results.” The Fetch Standard defines the request's [associated abort signal](https://fetch.spec.whatwg.org/) and rejection behavior. Application code still decides how to present that outcome. ## Connect cancellation to interface state This example cancels an earlier search before starting the next one: ```js let activeController = null; async function runSearch(query) { activeController?.abort("Superseded by a newer search"); activeController = new AbortController(); const controller = activeController; try { const data = await fetchJson( `/api/search?q=${encodeURIComponent(query)}`, { signal: controller.signal }, ); if (activeController === controller) { renderResults(data); } } catch (error) { if (controller.signal.aborted) { return; } showError(error); } finally { if (activeController === controller) { activeController = null; } } } ``` The identity check prevents an older request's cleanup from clearing the controller for a newer request. It also prevents stale results from replacing fresh ones if completion and user input race. The string passed to `abort()` becomes the signal's reason in supporting implementations. Without a custom reason, cancellation commonly rejects with a `DOMException` named `AbortError`. Checking `controller.signal.aborted` is appropriate here because this code owns that exact signal. Do not classify every caught `TypeError` as cancellation; `fetch` uses `TypeError` for other failures too. Browse [Web APIs](/web-apis/) for patterns that keep interface state tied to the current operation. ## Add a timeout without losing cleanup A timeout is one policy for triggering the same controller: ```js async function fetchJsonWithTimeout(url, milliseconds) { const controller = new AbortController(); const timer = setTimeout(() => { controller.abort(new Error("Request timed out")); }, milliseconds); try { return await fetchJson(url, { signal: controller.signal }); } finally { clearTimeout(timer); } } ``` Validate `milliseconds` before calling this helper; it should be a finite, non-negative number appropriate to the application. Clearing the timer in `finally` matters after success and every failure. Otherwise, a completed request leaves a callback waiting around to abort a controller nobody needs. Modern runtimes also provide helpers such as `AbortSignal.timeout()` and signal composition in some environments. Check the actual browser or runtime support required by your project before replacing an explicit controller. A tutorial's “modern” and your embedded webview's “modern” may not have met. ## Abort on component cleanup When a view, component, or route starts a request, abort it during that view's cleanup. The framework-specific hook differs, but the ownership rule stays simple: the scope that starts the operation should arrange to stop or ignore it when that scope ends. Aborting prevents your client from continuing to wait or consume a body. It does **not** guarantee that the server stopped processing, and it cannot reverse a database update or payment already accepted. Use idempotency, server-side cancellation, transaction design, or application-specific safeguards for consequential operations. ## Test all four exits Test successful JSON, an HTTP error, invalid JSON, and cancellation. Also start request A, immediately start request B, and confirm A cannot overwrite B. Use developer tools to slow the network rather than hoping your connection has an introspective afternoon. Our [optional-chaining guide](/javascript-optional-chaining/) explains the cleanup call `activeController?.abort()` and when that convenience would hide a missing requirement. Continue through [language tools](/language-tools/) when the control flow is correct but the syntax still deserves inspection. ## Frequently asked questions **How do I cancel a JavaScript fetch request?** Create an `AbortController`, pass `controller.signal` in the fetch options, and call `controller.abort()` from the relevant cancel or cleanup path. The fetch operation rejects if it has not completed, and body consumption can also be interrupted. Handle the rejection deliberately. Create a fresh controller for another request; once a signal is aborted, it remains aborted and cannot be reset. **Does fetch reject for a 404 or 500 response?** Normally, `fetch()` resolves to a `Response` when an HTTP response arrives, including error statuses such as 404 or 500. Check `response.ok` or inspect `response.status` and throw or return an application-specific result. Network failure, cancellation, an HTTP error response, and failure to parse the body are different outcomes. Keeping them distinct produces better retries, messages, logging, and tests. **Can I reuse an AbortController after aborting it?** No. Calling `abort()` permanently marks that controller's signal as aborted. Passing the same signal to a later operation causes it to begin in an aborted state. Create a new controller for every new request or operation. One live signal can intentionally control multiple operations that should cancel together, but aborting it cancels all participating operations and it still cannot be reset afterward. **Does aborting fetch cancel work on the server?** Not necessarily. Aborting tells the client to stop waiting for or consuming the operation, and the connection behavior may let a server notice disconnection. The server may already have received and processed the request. Cancellation cannot undo a database write, message, booking, or payment. Consequential operations need server-side safeguards such as idempotency, transactions, explicit cancellation support, or other application-specific design. **How should I implement a fetch timeout?** Use an abort signal triggered after a validated timeout, and clear the timer in `finally` after success, cancellation, or failure. Some current runtimes provide `AbortSignal.timeout()` and signal-composition helpers, but required browser or embedded-runtime support must be checked. Distinguish a timeout from user cancellation when the interface needs different messages. A timeout only ends the client's wait; it does not prove the server stopped processing. --- # Async forEach in JavaScript: Loops That Wait > JavaScript's forEach() does not wait for promises returned by an async callback. Use for...of with await when operations must run one after another. Use Promise.all() with map() for independent operations whose results you need together, or Promise.allSettled() when you need every success and failure. Decide the sequencing and failure policy before changing the loop. Source: https://programatium.com/javascript-async-foreach/ Publisher: Programatium Workshop Published: 2026-09-08 Updated: 2026-09-08 ## Why does async forEach finish before the work? JavaScript's `forEach()` does not wait for promises returned by an async callback. Use `for...of` with `await` when operations must run one after another. Use `Promise.all()` with `map()` for independent operations whose results you need together, or `Promise.allSettled()` when you need every success and failure. Decide the sequencing and failure policy before changing the loop. This tutorial uses dense arrays: every position contains an item, and the input stays unchanged while the operation runs. The examples use local mock functions, not network requests. The code and failure cases were checked with Node.js v22.18.0; that is the verification environment, not a claim about the minimum supported Node version. ## What exactly is wrong with the callback? Run this self-contained example in a JavaScript file: ```js async function loadLabel(id) { await Promise.resolve(); return "item-" + id; } const labels = []; [1, 2, 3].forEach(async (id) => { labels.push(await loadLabel(id)); }); console.log(labels.length); // 0 ``` The immediate log runs before any callback reaches `labels.push()`. The mock deliberately includes an asynchronous suspension so the missing wait is observable without timers or a remote service. There are two separate return values to consider. Each async callback returns a promise. The outer `forEach()` call returns `undefined`, discarding callback results. MDN documents this distinction in its [forEach reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach). Consequently, `await items.forEach(async (...) => ...)` does not join those callback promises. It awaits the outer return value. Whether a particular small example appears finished afterward depends on its scheduling; apparent success does not establish a reliable completion contract. An [async function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) also turns an uncaught exception into a rejected promise. A `try...catch` around the original synchronous `forEach()` call does not collect those rejected callback promises. Replacing the loop must address errors as well as timing. ## When should operations run sequentially? Use this helper when the next operation must not start until the previous one succeeds: ```js async function loadSequential(ids, load) { const values = []; for (const id of ids) { values.push(await load(id)); } return values; } ``` Here `load` is a function taking one ID and returning its result or a promise for that result. It must represent the complete operation, not launch detached work and return early. For `[1, 2, 3]`, the helper calls `load(1)`, waits, then calls `load(2)`, and finally `load(3)`. If the second call rejects, the third is never called. Earlier successful side effects are not reversed; the helper itself simply rejects instead of returning its local result array. This is useful for an ordered workflow, such as processing records where each step updates shared state needed by the next. It is also the simplest implementation when overlapping work is explicitly forbidden. The placement of [await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) is the mechanism: it suspends this async function until the awaited value settles. It does not freeze every other task in the application. Keep the operation call inside the loop. Creating all the promises first and then awaiting them individually does not prevent their underlying work from having started already. Sequence the calls, not merely the reading of their results. ## When is Promise.all appropriate? For independent work, collect the operation promises and await the collection: ```js async function loadConcurrent(ids, load) { return Promise.all(ids.map(async (id) => load(id))); } ``` The async mapper returns each operation's result. Its wrapper also converts a synchronous exception from `load` into a rejected mapper promise. That keeps an operation-level failure in the same promise-based error path. [Promise.all](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) produces fulfillment values in input order, even when completion order differs. One rejection rejects the aggregate promise; it does not cancel other operations. In this helper, `map()` invokes the operation for every input without awaiting previous results. `Promise.all()` aggregates the resulting promises; it is not a task scheduler that decides when to start them. Do not accidentally remove the mapper's return when adding braces. The expression `async (id) => load(id)` returns the operation. A block body needs `return load(id)` or an awaited result that it returns. Merely calling `load(id)` inside that block can detach the actual work from the promise being collected. Choose this helper when the application treats the collection as unsuccessful if any item fails. If partial results have value, use the next helper instead. Neither choice implies that a failed collection is safe to retry wholesale when operations have side effects. ## How do you preserve both successes and failures? Use a settled-outcome report when every item needs an explicit result: ```js async function loadOutcomes(ids, load) { const results = await Promise.allSettled( ids.map(async (id) => load(id)) ); return results.map((result, index) => ({ id: ids[index], ...result })); } ``` For these dense, unchanged input arrays, each output retains its original ID. The result has either `status: "fulfilled"` and `value`, or `status: "rejected"` and `reason`. The [allSettled reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled) defines those fields and the input-order guarantee. A rejected item is now report data, not a thrown aggregate rejection. The caller must inspect statuses. Counting the report's length alone would incorrectly count failed items as successful. For an import preview, an application might display successful rows and separately mark rejected rows for review. That is an application policy, not a rule supplied by JavaScript. Keep an internal error reason separate from a user-facing message: do not indiscriminately display raw service responses or confidential details. Avoid making `catch(() => undefined)` your default recovery policy. It can erase the distinction between an operation that legitimately produced no value and one that failed. ## How can you avoid launching everything at once? For a small teaching example, fixed-size batches make the launch boundary explicit: ```js async function loadBatches(ids, load, batchSize = 2) { if (!Number.isSafeInteger(batchSize) || batchSize < 1) { throw new RangeError("batchSize must be a positive safe integer"); } const values = []; for (let start = 0; start < ids.length; start += batchSize) { const batch = ids.slice(start, start + batchSize); const loaded = await Promise.all( batch.map(async (id) => load(id)) ); values.push(...loaded); } return values; } ``` With five IDs and a batch size of two, calls start in groups of two, two and one. A new group starts only after the preceding group fulfills. If one item rejects, no later batch starts, but another operation in that failing batch can still be running. The default of two is an illustration, not a recommended limit for a particular API. Read the service's current concurrency and rate-limit rules before selecting a production policy. A batch boundary is not a requests-per-second limit. This helper also waits for the slowest successful item in a batch before starting another batch; it is not a continuously replenished worker pool. Prefer this simple form when its scheduling behavior matches the requirement. ## How do you run the helpers together? Place the helper definitions above in one file, followed by this demonstration. It performs no external writes or requests: ```js async function demo() { const ids = [1, 2, 3]; console.log(await loadSequential(ids, loadLabel)); console.log(await loadConcurrent(ids, loadLabel)); console.log(await loadBatches(ids, loadLabel, 2)); const report = await loadOutcomes(ids, async (id) => { if (id === 2) throw new Error("Example rejection"); return loadLabel(id); }); console.log(report.map(({ id, status }) => [id, status])); } demo().catch(console.error); ``` The first three logs each contain `["item-1", "item-2", "item-3"]`. The final log pairs ID 1 with `"fulfilled"`, ID 2 with `"rejected"`, and ID 3 with `"fulfilled"`. If you included the initial broken example, its separate `0` log appears before these demonstration results. The final catch is a demonstration error boundary. In an application, return or await the helper from the caller that owns the operation, and implement that caller's reporting policy. Do not declare completion before its promise settles. ## What should a regression test prove? Test behavior, not elapsed milliseconds. The following cases were checked locally alongside the examples: | Test input or condition | Required observation | | --- | --- | | Empty array | Each helper returns an empty array | | Second sequential operation rejects | Only IDs 1 and 2 were called | | Concurrent operations resolve in reverse order | Returned values remain in input order | | One concurrent operation rejects | Another already-started operation can still finish | | Mixed settled outcomes | IDs and statuses remain paired in input order | | Batch size zero or fractional | The batch helper rejects before calling the operation | For ordering tests, use promises whose resolve functions the test controls. Resolve ID 3 first, then ID 2, then ID 1. This creates a deterministic completion sequence without assuming that a timer or network request meets a deadline. If the actual operation uses Fetch, handle HTTP response validation and cancellation separately; see [canceling Fetch with AbortController](/cancel-fetch-abortcontroller/). An aggregate promise is not an abort mechanism. For nullable input properties, [optional chaining](/javascript-optional-chaining/) explains access syntax, but required input validation still belongs before the work starts. Select the helper by three questions: must calls be sequential, must every outcome be retained, and how much work may be in flight? Those answers define the loop more reliably than replacing one keyword. ## Sources - [MDN: Array.prototype.forEach](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach) - [MDN: async function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) - [MDN: await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) - [MDN: Promise.all](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) - [MDN: Promise.allSettled](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled) ## Frequently asked questions **Does await forEach wait for async callbacks?** No. The forEach call returns undefined rather than a promise representing its callbacks. Awaiting that return value does not collect callback completion or rejection. In the local example, logging the output immediately produces zero items. Choose a helper that returns a promise for the complete operation, then await that helper. **When should I use for...of instead of Promise.all?** Use the sequential helper when the next call must wait for the previous call to succeed. For example, the supplied failure test stops before calling ID 3 when ID 2 rejects. Use the concurrent helper only when the operations can overlap and its aggregate failure policy suits the caller. **Does Promise.all cancel work after a rejection?** No. In the local rejection test, one operation rejects while another already-started operation remains pending and can later finish. Cancellation needs support from the underlying operation, such as an appropriate AbortSignal for Fetch. A failed aggregate also does not reverse side effects that earlier operations already completed. **How do I keep partial successes in an async loop?** The loadOutcomes helper returns an ID alongside each settled outcome. Inspect status before reading value or reason, and decide explicitly what the application does with failed items. In the demonstration, IDs 1 and 3 succeed while ID 2 is marked rejected. The report is not automatically an all-success result. **Is a batch size of two a rate limit?** No. Two is the teaching example's maximum number of operations launched in one batch, not a requests-per-second allowance. The next batch waits for that group's success. Select production limits using the service's documented rules; this simple helper does not implement timed rate limiting, retries or a continuously replenished worker pool. --- # JavaScript Optional Chaining: Uses and Traps > Optional chaining (`?.`) accesses a property, computed element, or optional function when the value immediately to its left may be `null` or `undefined`; the chain returns `undefined` instead of throwing at that point. Add `?.` at each nullable step, and pair it with `??` when only nullish values should receive a fallback. Optional calls still throw if a present value is not callable, undeclared root variables still fail, and grouping can end short-circuit protection. Use optional chaining for expected absence, not to hide malformed required data that should be validated. Source: https://programatium.com/javascript-optional-chaining/ Publisher: Programatium Workshop Published: 2026-07-19 Updated: 2026-08-26 ## Use `?.` for genuinely optional paths Optional chaining accesses a property, element, or method only when the value immediately to its left is not `null` or `undefined`. Otherwise, that chain returns `undefined` instead of throwing at that access. ```js const city = user?.profile?.address?.city; const firstItem = order?.items?.[0]; const result = plugin?.initialize?.(); ``` The three forms are property access (`object?.property`), computed access (`object?.[key]`), and optional call (`functionValue?.()`). MDN's [operator reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) documents all three. Use the operator when missing data is part of the contract: an account may have no avatar, an analytics callback may be absent, or an API field may be optional. If a value is required, silently producing `undefined` can move a useful error farther from its cause. ## Pair it with `??`, not an automatic `||` Optional chaining returns `undefined` for a missing path. Nullish coalescing supplies a fallback only for `null` or `undefined`: ```js const displayName = user?.profile?.displayName ?? "Anonymous"; const itemCount = cart?.items?.length ?? 0; ``` By contrast, `||` replaces every falsy value, including `0`, `""`, and `false`: ```js const savedVolume = settings?.volume ?? 50; // keeps 0 const wrongVolume = settings?.volume || 50; // replaces 0 ``` Choose based on the data contract. An empty display name may deserve a fallback, while a zero volume is likely deliberate. Syntax cannot decide product meaning; it has enough responsibilities already. ## Add `?.` at every nullable step This expression protects only `user`: ```js const city = user?.profile.address.city; ``` If `user` exists but `profile` is `null`, accessing `.address` throws. Write `user?.profile?.address?.city` only when each step may be absent. If `profile` must exist whenever `user` exists, validate that invariant instead of decorating the entire path with question marks. Optional chaining short-circuits one continuous chain. Grouping can end that protection: ```js const profile = user?.profile; // profile may be undefined const name = profile.name; // then this can throw const alsoUnsafe = (user?.profile).name; ``` Store and check the intermediate value, or keep the intended access in one chain. ## Optional call checks absence, not callability This is safe when `onSave` is `null` or `undefined`: ```js hooks.onSave?.(record); ``` It still throws a `TypeError` when `hooks.onSave` exists but contains a string, object, or other non-function value. Validate unknown plugin or API data before calling: ```js if (typeof hooks.onSave === "function") { hooks.onSave(record); } ``` Optional call is excellent for a callback whose type is already guaranteed but whose presence is optional. It is not runtime schema validation in a small hat. Our [cancelable-fetch tutorial](/cancel-fetch-abortcontroller/) uses `activeController?.abort()` because the variable intentionally starts as `null` and later holds an `AbortController`. ## Know what it cannot protect An undeclared root identifier still throws: ```js missingVariable?.name; // ReferenceError ``` Declare the variable first, even if its value is `undefined`. Optional chaining also cannot appear on the left side of a normal assignment: ```js user?.profile = {}; // SyntaxError ``` And optional chaining cannot be used as the constructor target of `new`. Use an explicit condition when a constructor itself is optional. These restrictions keep assignment and construction behavior unambiguous. Side effects inside a skipped computed access do not run: ```js let index = 0; const value = maybeList?.[index++]; // index stays 0 when maybeList is null or undefined ``` Avoid relying on that subtlety for important state changes. An explicit branch is easier to read and test. ## Do not hide malformed required data Suppose an order response must contain a customer ID. This code conceals a contract failure: ```js const customerId = response?.order?.customer?.id; ``` Validate at the boundary instead: ```js function requireCustomerId(response) { const id = response?.order?.customer?.id; if (typeof id !== "string" || id.length === 0) { throw new Error("Response is missing a customer ID"); } return id; } ``` Optional chaining can help inspect the path inside validation; it should not replace the validation. Browse [language tools](/language-tools/) for similar operator boundaries and [Web APIs](/web-apis/) for applying them to external data. The practical rule is short: use `?.` when absence is expected and has a defined meaning. Raise, validate, or branch when absence signals a bug. Fewer crashes are good; fewer clues are not the same achievement. ## Frequently asked questions **What does optional chaining return when data is missing?** When the value immediately before `?.` is `null` or `undefined`, that continuous optional chain short-circuits and evaluates to `undefined`. It does not substitute another value automatically. Use nullish coalescing, such as `user?.name ?? "Anonymous"`, when a fallback is appropriate. If a later step can also be nullish, add optional chaining there or validate the required structure explicitly. **What is the difference between `??` and `||` after `?.`?** Nullish coalescing (`??`) uses its fallback only when the left value is `null` or `undefined`. Logical OR (`||`) uses the fallback for every falsy value, including zero, an empty string, and false. Choose according to the data contract. A saved volume of zero should usually survive `??`, while an empty display name might deliberately receive a fallback after a separate rule. **Does `callback?.()` verify that callback is a function?** No. Optional call skips the call only when the value is `null` or `undefined`. If the property exists but contains a string, object, or another non-callable value, JavaScript still throws a `TypeError`. Use optional call when type is already guaranteed and presence alone is optional. Validate unknown input with `typeof callback === "function"` or a proper schema before calling it. **Why can grouped optional chaining still throw?** Short-circuit protection applies through one continuous optional chain. Grouping an intermediate expression ends that chain, so `(user?.profile).name` first produces `undefined` when the profile is absent and then tries to access `.name` on `undefined`. Keep the intended nullable access in one chain, such as `user?.profile?.name`, or store the intermediate result and check it explicitly before further access. **When should I avoid optional chaining?** Avoid using it to conceal missing data that violates an application contract. If an order must have a customer ID, returning `undefined` can move the failure far from the broken response. Validate the structure at the boundary and throw a clear error. Also prefer an explicit branch when skipped side effects would be surprising. Optional chaining is for expected absence with defined behavior, not universal error suppression. ---