Advanced Async Patterns & Web Workers
Scale your asynchronous architecture to production. Master the 4 Promise combinators (all, allSettled, race, any), implement clean cancellation with AbortController, and escape single-threaded UI freezes with true browser multi-threading via Web Workers.
Multi-Threaded Web Worker & Message Passing Architecture
Web Workers run on dedicated operating-system background threads completely isolated from the browser's Main UI thread. They communicate exclusively via serialized message passing (or zero-copy Transferable Objects).
1. The 4 Promise Combinators
When orchestrating multiple concurrent asynchronous operations, choose the exact combinator matching your business failure tolerance:
| Combinator | Settles When... | Fails When... | Ideal Production Use Case |
|---|---|---|---|
Promise.all() |
ALL promises fulfill. Returns array of all values. | ANY single promise rejects (Fail-Fast). | Transactional batches (e.g. user details + cart + shipping must all succeed). |
Promise.allSettled() |
ALL promises settle (fulfilled OR rejected). | Never fails! Returns array of { status, value/reason }. |
Bulk reporting, sending bulk notifications, dashboard widget aggregations. |
Promise.race() |
Whichever promise settles FIRST (fulfilled OR rejected). | If the first promise rejects. | Request timeouts (race fetch against a sleep rejection timer). |
Promise.any() |
Whichever promise FULFILLS first (ignores rejections). | Only if ALL promises reject (throws AggregateError). |
Fastest mirror lookup (querying 3 CDN endpoints for the quickest response). |
Practical Combinator Examples
const fetchUser = fetch('/api/user').then(r => r.json());
const fetchPrefs = fetch('/api/prefs').then(r => r.json());
// 1. Promise.allSettled: Never crashes your entire dashboard!
const results = await Promise.allSettled([fetchUser, fetchPrefs]);
results.forEach(res => {
if (res.status === 'fulfilled') {
console.log('Success payload:', res.value);
} else {
console.warn('Failed gracefully with reason:', res.reason.message);
}
});
// 2. Promise.any: Query fastest CDN mirror
const mirrorA = fetch('https://cdn-us.aicodelab.tech/asset.png');
const mirrorB = fetch('https://cdn-eu.aicodelab.tech/asset.png');
const fastestResponse = await Promise.any([mirrorA, mirrorB]);
Try it in Playground
2. Request Cancellation with AbortController
In modern web development, users frequently type in search boxes or navigate away before a request finishes. Without cancellation, outdated network responses overwrite newer state (race condition). The AbortController API solves this cleanly:
Canceling In-Flight Fetch Requests
const controller = new AbortController();
async function performSearch(query) {
try {
const res = await fetch(`/api/search?q=${query}`, {
// Connect fetch to abort signal:
signal: controller.signal
});
return await res.json();
} catch (err) {
if (err.name === 'AbortError') {
console.log('Search aborted by user or superseded by new query.');
} else {
throw err;
}
}
}
// User types a new letter: abort the previous search instantly!
controller.abort();
Try it in Playground
3. Web Workers: True Multi-Threading in JavaScript
If your web application performs intensive mathematical computing (image manipulation, cryptographic proof generation, physics simulation), running it on the main thread drops frames to 0fps. Web Workers execute code on an independent OS thread:
Instantiating and Communicating with a Worker
// In main.js (UI Thread):
const worker = new Worker('worker.js');
// Send work payload to background thread:
worker.postMessage({ numbers: [999999, 1000000] });
// Listen for results from worker thread:
worker.onmessage = (event) => {
console.log('Prime count computed by background thread:', event.data.result);
};
// ----------------------------------------------------
// In worker.js (Background Thread - Zero DOM access):
self.onmessage = (event) => {
const [min, max] = event.data.numbers;
// Heavy computation runs on OS core without freezing UI:
let count = 0;
for (let i = min; i <= max; i++) {
if (isPrime(i)) count++;
}
// Post message back to UI thread:
self.postMessage({ result: count });
};
Try it in Playground
Interactive Mini-Lab: Promise Combinator Battle Arena
Three concurrent tasks run: Task A (Fast, 200ms), Task B (Slow, 600ms), and Task C (Fails at 100ms). Compare how each combinator resolves:
Hands-on Challenge: Concurrency Pool Limiter
Coding Challenge
Firing 100 HTTP requests simultaneously via Promise.all() can crash backend servers or trigger 429 Too Many Requests rate-limiting. Build a concurrency pool function asyncPool(limit, items, asyncFn) that executes tasks in parallel with a strict cap of limit concurrent jobs:
- Process an array of
itemsusingasyncFn(item). - At any given moment, no more than
limitpromises should be pending. - When one job finishes, the next pending item immediately begins.
- Return an array of all results in the same index order as the input items!
/**
* Runs tasks with a strict concurrency limit using modern Promise pooling.
*/
async function asyncPool(limit, items, iteratorFn) {
const results = [];
const executing = new Set();
for (const item of items) {
// 1. Kick off the task and store its promise:
const p = Promise.resolve().then(() => iteratorFn(item));
results.push(p);
executing.add(p);
// Clean up when settled:
const clean = () => executing.delete(p);
p.then(clean, clean);
// 2. If pool limit reached, wait for at least one executing task to finish:
if (executing.size >= limit) {
await Promise.race(executing);
}
}
// 3. Wait for all queued results to settle:
return Promise.all(results);
}
// Verification:
const urls = [1, 2, 3, 4, 5, 6, 7, 8];
const downloadFile = id => new Promise(res => {
console.log(`Starting file #${id}`);
setTimeout(() => {
console.log(`Finished file #${id}`);
res(`Data #${id}`);
}, 200);
});
// Run with max 2 concurrent downloads at a time:
asyncPool(2, urls, downloadFile).then(allResults => {
console.log('All files downloaded:', allResults);
});
Chapter 26 Knowledge Check
Validate your mastery of Promise combinators, cancellation, and Web Worker threads.
Promise.all()
Promise.allSettled()
Promise.race()
Promise.any()
Promise.race() and Promise.any()?Promise.race() only works on network requests; Promise.any() works on timers.
Promise.race() settles on the first promise to settle (even if it rejects); Promise.any() ignores rejections and resolves on the first promise to FULFILL.
Promise.any() is deprecated in favor of Promise.all().
document.getElementById())?window or document; they must communicate results to the main thread via postMessage().
transferable: true.
fetch() HTTP requests?fetch.cancel()
AbortController and its signal property.
window.stopNetwork()
clearTimeout()
Promise.any(), what error is thrown?TypeError
SyntaxError
AggregateError (containing an errors array of all rejection reasons).
RangeError