Asynchronous JavaScript Skill
Quick Reference Card
Event Loop Order
1. Call Stack (sync code)
2. Microtasks (Promise.then, queueMicrotask)
3. Macrotasks (setTimeout, setInterval, I/O)
console.log('1'); // Sync
setTimeout(() => console.log('2'), 0); // Macro
Promise.resolve().then(() => console.log('3')); // Micro
console.log('4'); // Sync
// Output: 1, 4, 3, 2
Promise Basics
// Create
const promise = new Promise((resolve, reject) => {
if (success) resolve(value);
else reject(new Error('Failed'));
});
// Chain
fetch('/api/data')
.then(res => res.json())
.then(data => process(data))
.catch(err => handleError(err))
.finally(() => cleanup());
Async/Await
async function fetchData() {
try {
const response = await fetch('/api/data');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} catch (error) {
console.error('Fetch failed:', error);
throw error;
}
}
Promise Combinators
// All must succeed
const [a, b, c] = await Promise.all([p1, p2, p3]);
// All results (even failures)
const results = await Promise.allSettled([p1, p2, p3]);
results.forEach(r => {
if (r.status === 'fulfilled') console.log(r.value);
else console.log(r.reason);
});
// First to settle
const fastest = await Promise.race([p1, p2, p3]);
// First to succeed
const firstSuccess = await Promise.any([p1, p2, p3]);
Parallel vs Sequential
// Sequential (slow)
const a = await fetch('/a');
const b = await fetch('/b');
// Parallel (fast)
const [a, b] = await Promise.all([
fetch('/a'),
fetch('/b')
]);
Production Patterns
Retry with Backoff
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (err) {
if (i === retries - 1) throw err;
await new Promise(r => setTimeout(r, 2 ** i * 1000));
}
}
}
Timeout
async function fetchWithTimeout(url, ms = 5000) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), ms);
try {
const res = await fetch(url, { signal: controller.signal });
return await res.json();
} finally {
clearTimeout(timeout);
}
}
Debounce
function debounce(fn, delay) {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn(...args), delay);
};
}
Rate Limiter
class RateLimiter {
constructor(limit, interval) {
this.tokens = limit;
setInterval(() => this.tokens = limit, interval);
}
async acquire() {
while (this.tokens <= 0) {
await new Promise(r => setTimeout(r, 100));
}
this.tokens--;
}
}
Troubleshooting
Common Issues
| Problem |
Symptom |
Fix |
| Unhandled rejection |
Console warning |
Add .catch() or try/catch |
| Sequential instead of parallel |
Slow execution |
Use Promise.all() |
Missing await |
Promise instead of value |
Add await |
| Race condition |
Inconsistent results |
Use proper sequencing |
Debug Checklist
// 1. Add timing
console.time('fetch');
await fetchData();
console.timeEnd('fetch');
// 2. Log promise state
promise.then(console.log).catch(console.error);
// 3. Check for missing await
const result = fetchData();
console.log(result); // Promise? Add await!
// 4. Trace async chain
async function debug() {
console.log('Step 1');
const a = await step1();
console.log('Step 2', a);
const b = await step2(a);
console.log('Done', b);
}
Error Handling Pattern
// Wrapper for tuple return
async function safeAsync(promise) {
try {
return [await promise, null];
} catch (error) {
return [null, error];
}
}
const [data, error] = await safeAsync(fetchData());
if (error) handleError(error);
Related
- Agent 04: Asynchronous JavaScript (detailed learning)
- Skill: functions: Callbacks and closures
- Skill: ecosystem: API integration