Frontend Engineering
Async Patterns in Production
Quiz: Promise Chain Output Challenge
advanced15 min read
The Promise Gauntlet
Time to battle-test your async mental model. These 10 challenges are ordered by difficulty — the first few warm you up, and by the end you'll be tracing through microtask queues in your sleep.
The rules: predict the exact console output before checking. Write it down. Walking through the execution step by step is the only way to build a reliable mental model. Guessing teaches you nothing.
Let's go.
Challenge 1: The Warmup
console.log('A');
Promise.resolve().then(() => console.log('B'));
console.log('C');
Quiz
Challenge 2: Chained Thens
Promise.resolve(1)
.then(val => {
console.log(val);
return val + 1;
})
.then(val => {
console.log(val);
return val + 1;
})
.then(val => {
console.log(val);
});
console.log('sync');
Quiz
Challenge 3: Promise.resolve vs new Promise
console.log('1');
new Promise(resolve => {
console.log('2');
resolve();
console.log('3');
}).then(() => {
console.log('4');
});
console.log('5');
Quiz
Challenge 4: Interleaved Microtasks
Promise.resolve().then(() => {
console.log('A');
Promise.resolve().then(() => console.log('B'));
});
Promise.resolve().then(() => {
console.log('C');
Promise.resolve().then(() => console.log('D'));
});
Quiz
Challenge 5: async/await Desugaring
async function foo() {
console.log('foo start');
await bar();
console.log('foo end');
}
async function bar() {
console.log('bar');
}
console.log('script start');
foo();
console.log('script end');
Quiz
Challenge 6: Error Propagation
Promise.resolve()
.then(() => {
console.log('A');
throw new Error('oops');
})
.then(() => {
console.log('B');
})
.catch(() => {
console.log('C');
})
.then(() => {
console.log('D');
});
Quiz
Challenge 7: Promise.all Rejection Timing
const p1 = new Promise(resolve =>
setTimeout(() => { console.log('p1'); resolve('p1'); }, 100)
);
const p2 = new Promise((_, reject) =>
setTimeout(() => { console.log('p2'); reject('p2'); }, 50)
);
const p3 = new Promise(resolve =>
setTimeout(() => { console.log('p3'); resolve('p3'); }, 150)
);
Promise.all([p1, p2, p3]).catch(err => console.log('caught:', err));
Quiz
Challenge 8: Microtask vs Macrotask Race
setTimeout(() => console.log('T1'), 0);
Promise.resolve()
.then(() => {
console.log('P1');
setTimeout(() => console.log('T2'), 0);
})
.then(() => {
console.log('P2');
});
setTimeout(() => console.log('T3'), 0);
Quiz
Challenge 9: await in a Loop
async function run() {
const values = [1, 2, 3];
console.log('start');
values.forEach(async (val) => {
const result = await Promise.resolve(val * 10);
console.log(result);
});
console.log('end');
}
run();
Quiz
Challenge 10: The Ultimate Puzzle
async function first() {
console.log('1');
await second();
console.log('2');
}
async function second() {
console.log('3');
await Promise.resolve();
console.log('4');
}
console.log('5');
first();
Promise.resolve().then(() => console.log('6'));
console.log('7');
Quiz
Bonus: The Trick Question
const promise = new Promise(resolve => {
resolve(1);
resolve(2);
resolve(3);
});
promise.then(console.log);
Quiz
How Did You Score?
If you got 8+ right on first try, your async mental model is solid. If you missed a few, trace through them again — the act of walking through the execution step by step is what builds the neural pathways.
The patterns that trip people up most:
- Microtasks always drain completely before any macrotask runs
- await always yields, even for already-resolved promises
- .catch() recovers the chain — it doesn't stay rejected
- forEach ignores async — it doesn't await callback return values
- Promise.all rejection doesn't cancel other promises
Master these five rules and you'll never be surprised by async output again.