JavaScript Concurrency
Understanding async programming in JavaScript and TypeScript.
The JavaScript Model
JavaScript is single-threaded but non-blocking.
Main Thread: ─execute─execute─[await]─execute─[await]─execute─
│ │
└──I/O───┘ (handled by runtime)
All JavaScript code runs on one thread. Long-running operations (network, timers) are handled outside the main thread by the runtime.
Core Mechanisms
| Mechanism |
Purpose |
| Event Loop |
Schedules and executes code |
| Promises |
Represent future values |
| async/await |
Clean syntax for promises |
| Web Workers |
True parallelism (separate threads) |
Quick Comparison
| Python |
JavaScript |
asyncio |
Event Loop (built-in) |
await |
await |
asyncio.gather() |
Promise.all() |
multiprocessing |
Web Workers |
ThreadPoolExecutor |
No direct equivalent |
Topics
Quick Examples
Parallel Fetches
// Fetch multiple resources concurrently
async function fetchUserData(userId) {
const [user, posts, comments] = await Promise.all([
fetch(`/api/users/${userId}`).then(r => r.json()),
fetch(`/api/posts?userId=${userId}`).then(r => r.json()),
fetch(`/api/comments?userId=${userId}`).then(r => r.json()),
]);
return { user, posts, comments };
}
Sequential Operations
// When order matters
async function processItems(items) {
const results = [];
for (const item of items) {
const result = await processItem(item);
results.push(result);
}
return results;
}
Rate-Limited Concurrency
// Process with limited parallelism
async function processWithLimit(items, limit, fn) {
const results = [];
const executing = [];
for (const item of items) {
const promise = fn(item).then(result => {
executing.splice(executing.indexOf(promise), 1);
return result;
});
results.push(promise);
executing.push(promise);
if (executing.length >= limit) {
await Promise.race(executing);
}
}
return Promise.all(results);
}
// Usage
await processWithLimit(urls, 5, fetchUrl);
Background Processing with Web Worker
// main.js
const worker = new Worker('worker.js');
worker.postMessage({ data: largeArray, operation: 'sort' });
worker.onmessage = (event) => {
console.log('Sorted:', event.data);
};
// worker.js
self.onmessage = (event) => {
const { data, operation } = event.data;
if (operation === 'sort') {
const sorted = data.sort((a, b) => a - b);
self.postMessage(sorted);
}
};
Common Patterns
Error Handling
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} catch (error) {
if (i === retries - 1) throw error;
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)));
}
}
}
Timeout
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), ms)
);
return Promise.race([promise, timeout]);
}
// Usage
const result = await withTimeout(fetch('/api/data'), 5000);
Debounce
function debounce(fn, delay) {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn(...args), delay);
};
}
// Usage
const debouncedSearch = debounce(search, 300);
input.addEventListener('input', (e) => debouncedSearch(e.target.value));
Throttle
function throttle(fn, limit) {
let inThrottle;
return (...args) => {
if (!inThrottle) {
fn(...args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Usage
const throttledScroll = throttle(handleScroll, 100);
window.addEventListener('scroll', throttledScroll);
When to Use What
Is it CPU-intensive?
├── Yes → Web Worker
└── No → Is it waiting for something?
├── Yes → async/await
└── No → Regular synchronous code
| Situation |
Solution |
| API calls |
async/await with fetch |
| Multiple API calls |
Promise.all() |
| Heavy computation |
Web Worker |
| UI updates during computation |
requestAnimationFrame + chunking |
| React long renders |
Concurrent features |
Best Practices
- Use async/await — Cleaner than .then() chains
- Use Promise.all() — For independent parallel operations
- Handle errors — Always catch promise rejections
- Avoid blocking — Keep main thread free for UI
- Use Web Workers — For CPU-intensive tasks
- Consider React concurrent — For smooth UI updates
Common Mistakes
// Bad: Sequential when parallel is possible
async function bad() {
const user = await fetchUser(); // Wait...
const posts = await fetchPosts(); // Then wait...
const comments = await fetchComments(); // Then wait...
}
// Good: Parallel
async function good() {
const [user, posts, comments] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments(),
]);
}
// Bad: forEach with async (doesn't wait)
items.forEach(async (item) => {
await processItem(item); // Doesn't wait!
});
// Good: for...of waits
for (const item of items) {
await processItem(item); // Actually waits
}
// Or if parallel is OK:
await Promise.all(items.map(item => processItem(item)));