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)

  1. Skim before labs or interviews.
  2. Drill gaps — reopen README.md → 1.17.a…1.17.e.
  3. Practice — 1.17-Exercise-Questions.md.
  4. Polish answers — 1.17-Interview-Questions.md.

Core vocabulary

TermOne-liner
SynchronousCode runs line by line; each line blocks until done
AsynchronousCode starts an operation and continues; result handled later
Event loopMechanism that checks queues and pushes callbacks onto the call stack
Call stackLIFO structure tracking current function execution
MacrotasksetTimeout, setInterval, I/O callbacks — lowest priority queue
MicrotaskPromise .then/.catch/.finally, queueMicrotask — higher priority queue
CallbackFunction passed to another function for later invocation
PromiseObject representing a future value — pending, fulfilled, or rejected
async/awaitSyntactic sugar over Promises — makes async code read like sync
Callback hellDeeply nested callbacks — hard to read and maintain
Inversion of controlHanding 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

MethodResolves whenRejects whenUse case
Promise.all([...])All fulfillAny rejects (fail-fast)All must succeed
Promise.allSettled([...])All settleNeverGet every result
Promise.race([...])First settlesFirst settles (if reject)Timeout pattern
Promise.any([...])First fulfillsAll rejectFirst 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

PitfallFix
Missing awaitValue is a Promise, not the result — add await
await in forEachUse for...of (sequential) or Promise.all + map (parallel)
Sequential awaits for independent opsUse Promise.all for parallel
No error handlingAdd 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:

  1. Current synchronous code finishes
  2. All pending microtasks drain

Recursive setTimeout vs setInterval

AspectsetIntervalRecursive setTimeout
Gap measured fromStart of callbackEnd of callback
Drift riskYesNo
Overlap riskYes (if callback > interval)No
// Recursive setTimeout — guaranteed gap
function tick() {
  doWork();
  setTimeout(tick, 1000); // 1s after doWork finishes
}
setTimeout(tick, 1000);

Timer accuracy factors

FactorEffect
Call stack busyCallback waits until stack empty
Nested timers (depth > 4)Minimum 4ms enforced
Background tabThrottled 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

  1. Understand the model — single-threaded + event loop + two queues.
  2. Start with callbacks — foundational pattern, understand the problems.
  3. Use Promises — chainable, composable, standardized error handling.
  4. Prefer async/await — clean syntax, try...catch, parallel with Promise.all.
  5. Know your timers — setTimeout for delay, recursive setTimeout for intervals, always clearInterval.
  6. Always handle errors — try...catch or .catch() on every async path.

End of 1.17 quick revision.