Reference

Timing

Timing

withTimeout(promise, timeoutMs, labelOrOptions?) is a small helper for putting a wall-clock ceiling on an async operation. It rejects with a synthetic timeout error after timeoutMs and clears its internal timer when the wrapped promise settles first.

import { withTimeout } from "@openclaw/fs-safe/advanced";

#Signature

function withTimeout<T>(
  promise: Promise<T>,
  timeoutMs: number,
  labelOrOptions?: string | {
    label?: string;
    message?: string;
    createError?: () => Error;
  },
): Promise<T>;

If timeoutMs is 0, negative, Infinity, or NaN, the helper is a no-op and simply awaits the original promise.

#Examples

#Simple ceiling

import { withTimeout } from "@openclaw/fs-safe/advanced";

const buf = await withTimeout(
  fs.readFile("/srv/big.bin"),
  5_000,
  "read big.bin",
);

If the read doesn't resolve within 5 seconds, the returned promise rejects with Error: read big.bin timed out after 5000ms. The underlying fs.readFile continues until Node finishes it — withTimeout does not cancel the wrapped work, only the wait.

#Custom message

await withTimeout(work(), 5_000, {
  message: "build did not finish in time",
});

#Custom error factory

class BuildTimeout extends Error {}

await withTimeout(work(), 5_000, {
  createError: () => new BuildTimeout("build timeout (5s)"),
});

createError is called when the timer fires; the returned error is what the promise rejects with. Use this when callers branch on instanceof or want a custom cause.

#Cancellation

withTimeout does not abort the wrapped operation when the timer fires — it just stops waiting. If you need real cancellation, the wrapped operation must opt into an AbortSignal itself:

const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 5_000);

try {
  const res = await fetch(url, { signal: controller.signal });
  // ...
} finally {
  clearTimeout(timer);
}

For the common "I want a deadline AND cancellation" shape, use AbortSignal.timeout(ms) directly — it's the standard library's answer and handles both at once.

#Patterns

#Bound a credential refresh

const fresh = await withTimeout(
  refreshToken(currentRefresh),
  5_000,
  "refresh oauth token",
);
await writeSecretFileAtomic({ rootDir, filePath, content: JSON.stringify(fresh) });

#Compose with archive extraction

import { extractArchive } from "@openclaw/fs-safe/archive";

await extractArchive({
  archivePath,
  destDir,
  kind: "zip",
  timeoutMs: 30_000,
});

extractArchive already takes timeoutMs and uses withTimeout internally — you don't need to wrap it. Reach for withTimeout for operations that don't carry their own timeout knob.

#Disable in tests

When unit-testing flaky code, you might want to disable the timeout. Pass 0:

await withTimeout(work(), process.env.NODE_ENV === "test" ? 0 : 5_000, "work");

Better, gate it from the caller — withTimeout(p, 0, ...) returns the promise as-is.

#See also

  • Archive extractionextractArchive already takes timeoutMs.
  • Sidecar lockretry.maxAttempts × retry.maxDelayMs is a different form of bounded waiting.
  • AbortSignal.timeout — standard-library cancellation when you need to abort, not just give up.