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.md1.17.a1.17.e.
  3. Practice1.17-Exercise-Questions.md.
  4. Polish answers1.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 timerssetTimeout for delay, recursive setTimeout for intervals, always clearInterval.
  6. Always handle errorstry...catch or .catch() on every async path.

End of 1.17 quick revision.