bun/prefer-timeout-signal

Prefer AbortSignal.timeout() to an AbortController that only a timer aborts

AbortSignal.timeout(ms) replaces an AbortController and the setTimeout that aborts it with one call. Its signal also aborts with a TimeoutError instead of an AbortError, so the code that catches the error can tell a timeout from a cancellation.

A controller is reported if one setTimeout(() => controller.abort(), ms) is the only thing that aborts it, and the rest of the code only reads controller.signal.

Configure#

Off by default. To turn it on:

bunfig.toml
[lint.rules]
"bun/prefer-timeout-signal" = "error" # or "warn"

Every option, set to its default:

bunfig.toml
[lint.rules]
"bun/prefer-timeout-signal" = ["error", {
  factories = [], # list of strings
}]

Examples#

example.js1 problem
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);

const response = await fetch(url, { signal: controller.signal });
example.jsNo problems
const response = await fetch(url, { signal: AbortSignal.timeout(5000) });

A controller that something else can also abort isn't reported:

example.jsNo problems
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
cancelButton.addEventListener("click", () => controller.abort());

const response = await fetch(url, { signal: controller.signal });

Neither is one that aborts with its own reason:

example.jsNo problems
const controller = new AbortController();
setTimeout(() => controller.abort(new Error("The upload took too long")), 5000);

await upload(file, { signal: controller.signal });

Options#

OptionDefaultWhat it does
factories[]Names of your own functions that return an AbortController. Their return value is checked like new AbortController().
example.jswith { "factories": ["createController"] }1 problem
const controller = createController();
setTimeout(() => controller.abort(), 5000);

const response = await fetch(url, { signal: controller.signal });