Node.js Async Patterns & Error Handling 5 — Questions and Answers
Question 1: What is the correct error-first callback convention in Node.js?
- callback(result, error)
- callback(error, result) (Correct answer)
- callback(result) with a thrown error on failure
- callback({ error, result })
Correct answer: callback(error, result)
Node.js convention requires callbacks to receive the error as the first argument (null if none) and the result as the second.
Question 2: Which of the following creates a Promise that resolves after a given delay?
- Promise.delay(ms)
- new Promise(resolve => wait(ms, resolve))
- new Promise(resolve => setTimeout(resolve, ms)) (Correct answer)
- Promise.timeout(ms)
Correct answer: new Promise(resolve => setTimeout(resolve, ms))
`new Promise(resolve => setTimeout(resolve, ms))` uses the built-in `setTimeout` to delay resolution — there is no native `Promise.delay()`.
Question 3: What happens when you call `next(err)` in Express.js middleware?
- The request is retried from the beginning
- Control passes to the next error-handling middleware (a function with 4 parameters) (Correct answer)
- The server restarts
- The error is logged automatically and the response is ended
Correct answer: Control passes to the next error-handling middleware (a function with 4 parameters)
Passing any truthy value to `next()` skips remaining regular middleware and routes, forwarding to the nearest error-handling middleware (defined with `(err, req, res, next)`).
Question 4: What is the behavior of `Promise.all()` when one of its promises rejects?
- It waits for all promises to settle and returns partial results
- It immediately rejects with the error from the first rejected promise (Correct answer)
- It retries the rejected promise up to 3 times
- It resolves with undefined for the failed promise
Correct answer: It immediately rejects with the error from the first rejected promise
`Promise.all()` short-circuits on the first rejection: the returned promise immediately rejects with that error, even if other promises are still pending.
Question 5: Which async iteration construct should you use to consume a Node.js Readable stream line by line in modern Node.js?
- stream.on('data', callback)
- for await...of with the stream (Correct answer)
- stream.pipe() to a writable
- util.streamToArray(stream)
Correct answer: for await...of with the stream
Node.js streams implement the async iterator protocol, so `for await...of stream` is the idiomatic way to consume them asynchronously without callbacks.
Question 6: What is the risk of using `new Promise()` wrapper (the 'explicit promise construction antipattern')?
- It creates promises that cannot be resolved
- It unnecessarily wraps a promise that already exists, causing subtle error-swallowing bugs (Correct answer)
- It prevents garbage collection of the promise
- It converts microtasks into macrotasks
Correct answer: It unnecessarily wraps a promise that already exists, causing subtle error-swallowing bugs
Wrapping an existing promise with `new Promise()` is redundant and commonly leads to missed rejections if the inner promise's errors are not re-thrown.
Question 7: How do you propagate an error from a child async function to a parent async function in Node.js?
- Call process.emit('error', err) in the child
- Simply throw the error or let the rejected promise propagate — the parent's try/catch will catch it (Correct answer)
- Use EventEmitter to emit an 'error' event up the call stack
- Return the error object as the resolved value
Correct answer: Simply throw the error or let the rejected promise propagate — the parent's try/catch will catch it
In async/await, throwing in a child async function (or returning a rejected promise) causes the `await` expression in the parent to throw, which its `try/catch` handles.
What is the correct error-first callback convention in Node.js?