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 extraction —
extractArchivealready takestimeoutMs. - Sidecar lock —
retry.maxAttempts×retry.maxDelayMsis a different form of bounded waiting. AbortSignal.timeout— standard-library cancellation when you need to abort, not just give up.