Episode 1 — Fundamentals / 1.17 — Asynchronous Programming JavaScript
1.17 — Asynchronous Programming: Quick Revision
Compact cheat sheet. Print-friendly.
How to use this material (instructions)
- Skim before labs or interviews.
- Drill gaps — reopen
README.md→1.17.a…1.17.e. - Practice —
1.17-Exercise-Questions.md. - Polish answers —
1.17-Interview-Questions.md.
Core vocabulary
| Term | One-liner |
|---|---|
| Synchronous | Code runs line by line; each line blocks until done |
| Asynchronous | Code starts an operation and continues; result handled later |
| Event loop | Mechanism that checks queues and pushes callbacks onto the call stack |
| Call stack | LIFO structure tracking current function execution |
| Macrotask | setTimeout, setInterval, I/O callbacks — lowest priority queue |
| Microtask | Promise .then/.catch/.finally, queueMicrotask — higher priority queue |
| Callback | Function passed to another function for later invocation |
| Promise | Object representing a future value — pending, fulfilled, or rejected |
| async/await | Syntactic sugar over Promises — makes async code read like sync |
| Callback hell | Deeply nested callbacks — hard to read and maintain |
| Inversion of control | Handing your function to external code to call — risky |
Event loop execution order
1. Run all synchronous code (call stack)
2. Drain ALL microtasks (Promise .then, queueMicrotask)
3. Run ONE macrotask (setTimeout, setInterval)
4. Repeat from step 2
Rule: Microtasks always run before macrotasks when the stack is empty.
Callback cheat sheet
// Synchronous callback
[1, 2, 3].forEach(n => console.log(n));
// Asynchronous callback
setTimeout(() => console.log("later"), 1000);
// Error-first pattern (Node.js convention)
function asyncOp(callback) {
// callback(error, result)
callback(null, "success"); // no error
callback(new Error("fail")); // error
}
Promise cheat sheet
Creating
const p = new Promise((resolve, reject) => {
// resolve(value) → fulfilled
// reject(error) → rejected
});
Consuming
p.then(value => { /* success */ })
.catch(err => { /* error */ })
.finally(() => { /* cleanup */ });
Chaining
fetchUser(1)
.then(user => fetchPosts(user.id)) // return Promise → next .then waits
.then(posts => console.log(posts))
.catch(err => console.error(err)); // catches any error above
Static methods
| Method | Resolves when | Rejects when | Use case |
|---|---|---|---|
Promise.all([...]) | All fulfill | Any rejects (fail-fast) | All must succeed |
Promise.allSettled([...]) | All settle | Never | Get every result |
Promise.race([...]) | First settles | First settles (if reject) | Timeout pattern |
Promise.any([...]) | First fulfills | All reject | First success wins |
Shortcuts
Promise.resolve(42); // already fulfilled
Promise.reject(new Error("no")); // already rejected
Async/await cheat sheet
Basic pattern
async function load() {
try {
const res = await fetch("/api/data");
const data = await res.json();
return data;
} catch (err) {
console.error(err);
return null;
} finally {
hideSpinner();
}
}
Sequential vs parallel
// Sequential (~2s if each takes 1s)
const a = await fetchA();
const b = await fetchB();
// Parallel (~1s)
const [a, b] = await Promise.all([fetchA(), fetchB()]);
Common pitfalls
| Pitfall | Fix |
|---|---|
Missing await | Value is a Promise, not the result — add await |
await in forEach | Use for...of (sequential) or Promise.all + map (parallel) |
| Sequential awaits for independent ops | Use Promise.all for parallel |
| No error handling | Add try...catch or .catch() on the call |
Async function forms
async function f() {} // declaration
const f = async function() {}; // expression
const f = async () => {}; // arrow
class C { async method() {} } // method
Timer cheat sheet
setTimeout / setInterval
const tid = setTimeout(fn, delay); // one-time
clearTimeout(tid); // cancel
const iid = setInterval(fn, interval); // repeating
clearInterval(iid); // cancel
setTimeout(fn, 0) — not instant
Places callback in macrotask queue. Runs after:
- Current synchronous code finishes
- All pending microtasks drain
Recursive setTimeout vs setInterval
| Aspect | setInterval | Recursive setTimeout |
|---|---|---|
| Gap measured from | Start of callback | End of callback |
| Drift risk | Yes | No |
| Overlap risk | Yes (if callback > interval) | No |
// Recursive setTimeout — guaranteed gap
function tick() {
doWork();
setTimeout(tick, 1000); // 1s after doWork finishes
}
setTimeout(tick, 1000);
Timer accuracy factors
| Factor | Effect |
|---|---|
| Call stack busy | Callback waits until stack empty |
| Nested timers (depth > 4) | Minimum 4ms enforced |
| Background tab | Throttled to ~1000ms |
Common patterns
Debounce
function debounce(fn, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
Retry with backoff
async function retry(fn, attempts, delay) {
for (let i = 0; i < attempts; i++) {
try { return await fn(); }
catch (err) {
if (i === attempts - 1) throw err;
await new Promise(r => setTimeout(r, delay * (i + 1)));
}
}
}
Timeout wrapper
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error("Timeout")), ms)
);
return Promise.race([promise, timeout]);
}
Execution order quick reference
console.log("1"); // sync
setTimeout(() => console.log("2"), 0); // macrotask
Promise.resolve().then(() => console.log("3"));// microtask
console.log("4"); // sync
// Output: 1, 4, 3, 2
Master workflow
- Understand the model — single-threaded + event loop + two queues.
- Start with callbacks — foundational pattern, understand the problems.
- Use Promises — chainable, composable, standardized error handling.
- Prefer async/await — clean syntax,
try...catch, parallel withPromise.all. - Know your timers —
setTimeoutfor delay, recursivesetTimeoutfor intervals, alwaysclearInterval. - Always handle errors —
try...catchor.catch()on every async path.
End of 1.17 quick revision.