Free learning resource

Source code & resources

Every code example from Asynchronous JavaScript Mastery: From Callbacks to Streams — free to read, copy and run. No sign-up, no catch. If you found this from one of my courses, welcome; take the code and keep building.

Source code

Code from every lesson

25 examples, in lesson order.

1. Callbacks: Passing Functions as Instructions JavaScript

const announceHit = (target) => {
  console.log(`${target} took 25 damage!`);
};

const swingSword = (target, callback) => {
  console.log(`Swinging at ${target}...`);
  callback(target);
};

swingSword("Goblin", announceHit);

2. Error-First Callbacks and the Node Convention JavaScript

function openLootChest(chestId, callback) {
  setTimeout(() => {
    const loot = "Dragon Slayer Sword";
    callback(null, loot);
  }, 100);
}

openLootChest("chest_42", (err, item) => {
  if (err) {
    console.log("Loot failed:", err.message);
    return;
  }
  console.log("You found:", item);
});

console.log("Opening the chest...");

3. Callback Hell: When Nesting Goes Wrong JavaScript

function findHero(cb) {
  setTimeout(() => {
    cb(null, "Aerith");
  }, 40);
}

function pickWeapon(hero, cb) {
  setTimeout(() => {
    cb(null, "Princess Guard");
  }, 40);
}

findHero((err, hero) => {
  pickWeapon(hero, (err, weapon) => {
    console.log(hero + " readies " + weapon);
  });
});

4. Timers and Scheduling: setTimeout, setInterval, and Zero Delay JavaScript

console.log("Hero begins channeling Fireball");

setTimeout(() => {
    console.log("BOOM! 80 damage dealt!");
}, 1500);

console.log("Hero dodges while channeling");

5. Event Emitters: The Publish-Subscribe Pattern JavaScript

const { EventEmitter } = require('node:events');

const guild = new EventEmitter();

guild.on('questComplete', () => {
  console.log('Quest done! 100 XP earned!');
});

guild.emit('questComplete');

6. Creating and Consuming Promises JavaScript

const plate = new Promise((resolve, reject) => {
  resolve("Souffle is ready!");
});

plate.then((dish) => {
  console.log("Service:");
  console.log(dish);
});

console.log("Order fired...");

7. Chaining: Flattening the Pyramid JavaScript

const lootChest = Promise.resolve(100);

lootChest
  .then(gold => {
    console.log(`Found ${gold} gold!`);
    return gold * 2;
  })
  .then(doubled => {
    console.log(`Doubled to ${doubled}!`);
    return doubled + 50;
  })
  .then(final => {
    console.log(`Bonus! Total: ${final}.`);
  });

8. Error Propagation and finally JavaScript

const openCase = () =>
  Promise.resolve("Case file opened.");

const verifyLead = (msg) => {
  console.log(msg);
  throw new Error("Missing witness statement!");
};

const closeCase = () => {
  console.log("Case closed!");
};

openCase()
  .then(verifyLead)
  .then(closeCase)
  .catch(err => {
    console.log("Setback:", err.message);
  });

9. Running in Parallel: Promise.all and Promise.allSettled JavaScript

const openChest = (name, ms, loot) =>
  new Promise((resolve) => {
    setTimeout(() => resolve(loot), ms);
  });

async function main() {
  const drops = await Promise.all([
    openChest("Gold", 200, "120 Gold"),
    openChest("Gem", 300, "Ruby Shard"),
    openChest("Rune", 250, "Frost Rune"),
  ]);
  console.log("Loot acquired:");
  for (const item of drops) {
    console.log("  " + item);
  }
}

main();

10. Racing Promises: Promise.race and Promise.any JavaScript

const filing = (name, ms) =>
  new Promise((resolve) =>
    setTimeout(() => resolve(name), ms)
  );

async function main() {
  const winner = await Promise.race([
    filing("Reuters", 300),
    filing("AP", 200),
    filing("Bloomberg", 500),
  ]);
  console.log(`First to file: ${winner}!`);
}

main();

11. async and await: Synchronous-Looking Async Code JavaScript

const delay = (ms) =>
  new Promise(r => setTimeout(r, ms));

function loadUser() {
  return delay(80).then(() => ({
    name: 'Ada', hp: 100
  }));
}

function addRole(user) {
  return delay(60).then(() => ({
    ...user, role: 'admin'
  }));
}

loadUser()
  .then(user => addRole(user))
  .then(ready => {
    console.log(ready.name + ' is ready!');
    console.log('Role: ' + ready.role);
  });

12. Error Handling with try/catch/finally JavaScript

async function castSpell(spell) {
  if (spell === "fizzle") {
    throw new Error("Spell fizzled!");
  }
  return `${spell} hits for 42 damage!`;
}

async function main() {
  try {
    const result = await castSpell("fizzle");
    console.log(result);
  } catch (err) {
    console.log("Caught:", err.message);
  }
}

main();

13. Sequential vs Parallel await: A Common Performance Trap JavaScript

const fetchParcel = async (name, ms) => {
  await new Promise(r => setTimeout(r, ms));
  return `${name} delivered!`;
};

async function main() {
  const start = Date.now();
  const boston = await fetchParcel("Boston", 800);
  const denver = await fetchParcel("Denver", 800);
  const austin = await fetchParcel("Austin", 800);
  const total = Date.now() - start;
  console.log(boston);
  console.log(denver);
  console.log(austin);
  console.log(`Took ${total} ms total!`);
}
main();

14. Awaiting in Loops: forEach Traps and for-of Fixes JavaScript

const section = ["Violins", "Cellos", "Flutes"];

const tuneUp = async (name) => {
  await new Promise(r =>
    setTimeout(r, 100));
  console.log(`${name} tuned!`);
};

const main = async () => {
  section.forEach(async (name) => {
    await tuneUp(name);
  });
  console.log("Orchestra ready!");
};

main();

15. Cancellation with AbortController JavaScript

// Spawn a controller for our quest
const ctrl = new AbortController();
const sig = ctrl.signal;

// Live and ready, no abort yet
console.log("Quest open!");
console.log("Aborted?", sig.aborted);

// Hero pulls the rip cord
ctrl.abort();

console.log("Hero fled the battle!");
console.log("Aborted?", sig.aborted);

16. Macrotasks vs Microtasks: The Two Queues JavaScript

console.log('Power up requested');

Promise.resolve('mana boost').then(value => {
  console.log('Got:', value);
});

console.log('Battle continues');

17. queueMicrotask and Manual Scheduling JavaScript

console.log("Quest started");

queueMicrotask(() => {
  console.log("Loot picked up");
});

console.log("Quest ending");

18. Ordering Puzzles: Predicting the Output JavaScript

console.log("Hero enters dungeon");

setTimeout(() => {
    console.log("Trap triggers!");
}, 0);

Promise.resolve().then(() => {
    console.log("Shield blocks!");
});

console.log("Hero swings sword");

19. Starving the Loop: When Microtasks Attack JavaScript

// Boss attack scheduled in 50ms
setTimeout(() => {
    console.log("Boss strikes for 200 damage!");
}, 50);

let casts = 0;
const echoSpell = () => {
    casts = casts + 1;
    if (casts < 100000) {
        queueMicrotask(echoSpell);
    } else {
        console.log("Echo spell finished");
    }
};

queueMicrotask(echoSpell);
console.log("Player casts Echo!");

20. Offloading Heavy Work to Worker Threads JavaScript

function grindPrimes(limit) {
  let count = 0;
  for (let n = 2; n < limit; n++) {
    let prime = true;
    for (let d = 2; d * d <= n; d++) {
      if (n % d === 0) { prime = false; break; }
    }
    if (prime) count++;
  }
  return count;
}

const tick = setInterval(() => {
  console.log("Boss timer tick!");
}, 100);

console.log("Grinding primes...");
const total = grindPrimes(8_000_000);
console.log(`Found ${total} primes.`);
clearInterval(tick);

21. Async Generators and for-await-of JavaScript

const sleep = (ms) =>
  new Promise((r) => setTimeout(r, ms));
async function* lootStream() {
  yield "Iron Sword";
  await sleep(50);
  yield "Health Potion";
  await sleep(50);
  yield "Dragon Scale";
}
async function main() {
  const drops = lootStream();
  const first = await drops.next();
  console.log("Drop:", first.value);
  const second = await drops.next();
  console.log("Drop:", second.value);
}
main();

22. Building an Async Iterator by Hand JavaScript

const wait = ms =>
  new Promise(r => setTimeout(r, ms));

const dropper = {
  drops: ["Iron Sword", "Mana Potion"],
  i: 0,
  async next() {
    await wait(200);
    if (this.i >= this.drops.length) {
      return { value: undefined, done: true };
    }
    const v = this.drops[this.i++];
    return { value: v, done: false };
  }
};

async function main() {
  console.log(await dropper.next());
  console.log(await dropper.next());
  console.log(await dropper.next());
}
main();

23. Streaming Data and Backpressure JavaScript

async function* lootStream() {
  const drops = ["Sword", "Shield", "Gem"];
  for (const drop of drops) {
    await new Promise(
      (r) => setTimeout(r, 100)
    );
    yield drop;
  }
}

async function main() {
  for await (const item of lootStream()) {
    console.log(`Got: ${item}!`);
  }
}
main();

24. Combining Async Streams and Early Exit JavaScript

const sleep = (ms) =>
  new Promise((r) => setTimeout(r, ms));

async function* dungeonLoot(name, items) {
  for (const item of items) {
    await sleep(80);
    yield `${name}: ${item}`;
  }
}

async function main() {
  const cave = dungeonLoot(
    "Crystal Cave",
    ["Ruby", "Emerald", "Sapphire"]
  );
  for await (const drop of cave) {
    console.log(drop);
  }
}

main();

25. A Practical Async Pipeline End to End JavaScript

async function* fetchDungeons() {
  const pages = [
    ["Wraith", "Imp"],
    ["Ogre", "Lich"]
  ];
  for (const p of pages) {
    await new Promise(r =>
      setTimeout(r, 40));
    yield p;
  }
}

async function main() {
  for await (const page of fetchDungeons()) {
    console.log("Page:", page);
  }
}
main();