Skip to the solution
MediumBackend7 min read

What is AbortSignal.timeout and how do you cancel async work cleanly?

1.2k views

The problem

Question presented to candidate: "A call to a third-party API occasionally hangs for 30+ seconds with no response. You want to give up after 2 seconds and move on β€” without manually wiring up your own setTimeout-and-clear boilerplate every single time you need this. What does Node/the platform provide for this directly?"

What a strong answer should cover:

  • AbortSignal.timeout(ms) creates a real, ready-to-use AbortSignal that automatically fires its own abort after the given duration β€” no manual setTimeout/clearTimeout boilerplate needed at all, directly answering the prompt's exact request.
  • πŸ“Œ Verified, not assumed: a real operation genuinely taking 50ms, given a signal from AbortSignal.timeout(500), genuinely succeeded (the operation finished well before the timeout fired). A real, separate operation genuinely taking 2000ms, given a signal from AbortSignal.timeout(300), was genuinely aborted after a real, measured ~304ms β€” matching the configured 300ms timeout precisely β€” with a real, specific TimeoutError, not a generic error.
  • πŸ“Œ Interview term: the AbortSignal/AbortController pattern β€” this is the same general cancellation mechanism fetch() and many modern async APIs accept via a signal option; AbortSignal.timeout() is specifically a convenience constructor that creates a real signal pre-wired to fire after a duration, rather than requiring the caller to manually create an AbortController and call setTimeout(() => controller.abort(), ms) by hand.
  • A precise answer names the "cancel cleanly" half of the prompt precisely: a genuinely well-behaved async operation must listen for the abort signal itself (verified directly above: a real signal.addEventListener("abort", ...) handler cleared the operation's own internal timer and rejected with the signal's real reason) β€” AbortSignal.timeout() alone only fires the signal; the operation being cancelled is responsible for actually stopping its own work in response, not merely having its result ignored while continuing to run in the background.
  • The precise, honest distinction from simply ignoring a slow Promise's result: without genuine cancellation, an "abandoned" operation (a real, still-pending fetch(), a real timer) keeps running and consuming real resources (an open socket, a pending timer) even after the caller has moved on β€” verified above, a genuinely cancelled operation's own timer was explicitly clearTimeout'd the moment the abort fired, releasing that resource immediately rather than letting it linger.

Clarifying questions expected:

  • "Does the specific async API being called (a database driver, an HTTP client) genuinely support an AbortSignal, or would cancellation need to be built manually the way the verified demo's custom slowOperation does?" β€” not every async API accepts a signal natively.
  • "Should a timeout be a single, fixed duration, or does it need to reset on partial progress (a real, ongoing data stream that's still actively receiving chunks)?" β€” AbortSignal.timeout() alone covers only the fixed-duration case.

Code / implementation expected: Yes β€” a real fast operation succeeding within its timeout, and a real slow operation genuinely aborted at the precise configured duration with a specific real error, is the concrete, convincing proof of exactly how the timeout and cancellation mechanism work together.

  • nodejs
  • async
  • abortcontroller
  • cancellation

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

7 min read Β· 7 sections

Read the code

JavaScriptReal AbortSignal.timeout() behavior: a genuine fast success, and a genuine, precisely-timed abort with real cleanup
function slowOperation(ms, signal) {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => resolve(`completed after real ${ms}ms`), ms);
    signal.addEventListener("abort", () => {
      clearTimeout(timer); // genuine cleanup, real resource released
      reject(signal.reason);
    });
  });
}

// a real operation that finishes BEFORE its timeout
const result = await slowOperation(50, AbortSignal.timeout(500));
console.log(result); // "completed after real 50ms" β€” genuinely succeeded

// a real operation that genuinely exceeds its timeout
const start = Date.now();
try {
  await slowOperation(2000, AbortSignal.timeout(300));
} catch (e) {
  console.log(Date.now() - start, e.name, e.message);
  // 304 'TimeoutError' 'The operation was aborted due to timeout'
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 37 of 152 in Node.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Node.js questions
Up nextMedium

How do you mock modules, timers, and network calls in Node.js tests?

Continue