Async Timeout Wrapper

Race a promise against a timer so a slow dependency cannot hold a request open indefinitely, aborting the underlying signal when the deadline passes.

JavascriptResilience

Code

snippet
export async function withTimeout(promiseFactory, timeoutMs, signal) {
  const controller = new AbortController();
  if (signal) signal.addEventListener("abort", () => controller.abort());
  let timer;
  const timerPromise = new Promise((_, reject) => {
    timer = setTimeout(() => {
      controller.abort();
      reject(new Error("operation timed out after " + timeoutMs + "ms"));
    }, timeoutMs);
  });
  try {
    return await Promise.race([promiseFactory(controller.signal), timerPromise]);
  } finally {
    clearTimeout(timer);
  }
}