What is callback hell and how do you avoid it?
Callback hell is async steps nested inside each other's callbacks, so the code drifts right and error handling repeats at every level.
getUser(id, (err, user) => { if (err) return handle(err); getOrders(user, (err, orders) => { if (err) return handle(err); getInvoice(orders[0], (err, invoice) => { if (err) return handle(err); send(invoice); }); }); });Rewrite it with
async/awaitand onetry/catch, and run independent steps withPromise.all. More in callbacks.What is a promise in JavaScript?
A promise stands for a value that will be ready later. It is pending, fulfilled or rejected, and once settled it never changes; read it with
.then,.catchorawait.JavaScriptconst p = new Promise(resolve => setTimeout(() => resolve('done'), 10)); console.log('pending first'); p.then(v => console.log('then:', v)).finally(() => console.log('settled'));.thenreturns a new promise, which is what makes chaining work. See promises.How do
asyncandawaitwork, and how do you handle errors with them?An
asyncfunction always returns a promise.awaitpauses the function until a promise settles, then gives you the value or throws the rejection, so errors use an ordinarytry/catch.JavaScriptfunction getUser(id) { return new Promise((resolve, reject) => setTimeout(() => (id > 0 ? resolve({ id, name: 'Ada' }) : reject(new Error('bad id'))), 10)); } async function show(id) { try { const user = await getUser(id); console.log('got', user.name); } catch (err) { console.log('failed:', err.message); } finally { console.log('done', id); } } show(1).then(() => show(-1));awaitpauses only the current function, not the thread. See async and await.Explain the event loop in JavaScript.
JavaScript runs your code on one call stack. When the stack empties, the event loop runs every queued microtask (promise callbacks, code after an
await), then the next macrotask (a timer, I/O, a UI event), draining microtasks again after each one.JavaScriptconsole.log('script start'); setTimeout(() => console.log('timeout'), 0); Promise.resolve().then(() => console.log('promise')); queueMicrotask(() => console.log('microtask')); console.log('script end');So
timeoutprints last, after both microtasks. Diagrams in the event loop.In what order do
setTimeout(fn, 0)andPromise.thencallbacks run?JavaScriptconst log = []; log.push('A'); setTimeout(() => log.push('B'), 0); Promise.resolve().then(() => log.push('C')); log.push('D'); setTimeout(() => console.log(log.join(' ')), 10);Synchronous code runs first, pushing
AandD. Then the microtask queue drains (C), and only then does the timer run (B). Microtasks always run before the next macrotask, even with a0delay.What is the difference between
Promise.all,allSettled,raceandany?allfulfills when every promise does and rejects on the first rejection;allSettledwaits for all and never rejects;racesettles like the first one to settle;anyfulfills with the first success and rejects with anAggregateErroronly when all reject.JavaScriptconst ok = (v, ms) => new Promise(r => setTimeout(() => r(v), ms)); const fail = (msg, ms) => new Promise((_, rej) => setTimeout(() => rej(new Error(msg)), ms)); async function main() { console.log('all:', (await Promise.all([ok('a', 20), ok('b', 10)])).join(',')); try { await Promise.all([ok('a', 20), fail('boom', 10)]); } catch (e) { console.log('all rejected:', e.message); } const settled = await Promise.allSettled([ok('a', 20), fail('boom', 10)]); console.log('allSettled:', settled.map(r => r.status).join(',')); console.log('race:', await Promise.race([ok('slow', 30), ok('fast', 10)])); console.log('any:', await Promise.any([fail('x', 5), ok('first ok', 20)])); } main();Typical uses:
allwhen every result is needed,racefor timeouts.What is the difference between awaiting in sequence and in parallel?
Two
awaits in a row run one after the other, so the times add. Starting both and awaitingPromise.allruns them together, so you wait only for the slowest.JavaScriptconst wait = ms => new Promise(r => setTimeout(r, ms)); const elapsed = t => Math.round((Date.now() - t) / 100) * 100; async function main() { let t = Date.now(); await wait(100); await wait(100); console.log('sequential ~', elapsed(t), 'ms'); t = Date.now(); await Promise.all([wait(100), wait(100)]); console.log('parallel ~', elapsed(t), 'ms'); } main();A common bug is
array.forEach(async ...), which waits for nothing; usefor...oforPromise.all(array.map(...)).How do you convert a callback function into one that returns a promise?
Wrap the call in
new Promiseand resolve or reject from the callback. A genericpromisifydoes this for any function with a Node-style(err, result)callback.JavaScriptfunction promisify(fn) { return (...args) => new Promise((resolve, reject) => fn(...args, (err, result) => (err ? reject(err) : resolve(result)))); } function divideLater(a, b, cb) { setTimeout(() => (b === 0 ? cb(new Error('divide by zero')) : cb(null, a / b)), 10); } const divide = promisify(divideLater); divide(10, 2).then(r => console.log('result', r)); divide(1, 0).catch(e => console.log('error', e.message));Node.js ships the helper as
util.promisify.What happens to a promise rejection that nobody handles?
If a rejected promise has no handler when the microtask queue drains, the runtime reports an unhandled rejection: browsers log it and fire
unhandledrejection, and Node.js 15 and later crash the process by default unless anunhandledRejectionhandler is set (see--unhandled-rejections).Common causes are a promise started with no
awaitor.catch,asynccallbacks passed toforEach, andreturn promiseinsidetrywithoutawait, which thatcatchnever sees. Fix it where the promise is created.Can microtasks block the event loop?
Yes. The loop drains every microtask, including ones queued during the drain, before the next macrotask, so an endless chain starves timers, I/O and rendering.
JavaScriptlet ticks = 0; setTimeout(() => console.log('timer ran after', ticks, 'microtasks'), 0); function spin() { if (++ticks < 100000) queueMicrotask(spin); } spin();Split long work with
setTimeout(orsetImmediatein Node.js) between chunks, or move it to a worker.Does code after
await nullrun before the caller continues?JavaScriptconst log = []; async function f() { log.push('f1'); await null; log.push('f2'); } f(); log.push('main'); setTimeout(() => console.log(log.join(' ')), 0);An
asyncfunction runs synchronously until its firstawait, sof1goes in first.await nullschedules the rest as a microtask and returns to the caller, which pushesmain. Thenf2runs, before the timer prints.How do you cancel a
fetchrequest or other async work?Pass an
AbortController'ssignaltofetchand callcontroller.abort(); thefetchrejects with anAbortError. A promise cannot be cancelled, but the signal stops the work behind it.const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), 5000); try { const res = await fetch('/api/search?q=js', { signal: controller.signal }); console.log(await res.json()); } catch (err) { if (err.name === 'AbortError') console.log('cancelled'); else throw err; } finally { clearTimeout(timer); }AbortSignal.timeout(5000)replaces the manual timer; its error is aTimeoutError, as MDN'sAbortSignal.timeoutpage shows.
Async JavaScript Interview Questions
Callbacks, promises, async/await and the event loop, including the ordering questions.
12 questions