Skip to content

TypeScript · correctness

no-hand-rolled-sleep

eslint:no-hand-rolled-sleep

Disallow uncancellable promisified timers and timeout arms.

Default
error
Fix
none
Languages
typescript

Why

A timer that outlives an aborted operation or a lost promise race retains work and can keep the process alive until it fires.

Fix

Use `node:timers/promises` with an abort signal for delays, or pass `AbortSignal.timeout(...)` to the timed operation.

Before / after

Executed by this rule’s unit tests.

Before

A Promise wraps a timer without cancellation

src/lib/queue.ts · focus
await new Promise((resolve) => setTimeout(resolve, 500));

After

A standard-library timer accepts an abort signal

src/lib/queue.ts · focus
import { setTimeout as sleep } from "node:timers/promises";
await sleep(500, undefined, { signal });

Limits

  • The rule skips tests, scripts, generated files, and client modules by default, and supports explicit path exemptions.

Message IDs

handRolledSleephandRolledTimeoutRace

Options

{
  "additionalProperties": false,
  "properties": {
    "allowIn": {
      "description": "Glob patterns for modules exempt from the rule (e.g. a single sanctioned `sleep` utility). Matched against the ABSOLUTE file path, so anchor with a `**/` prefix (e.g. `**/lib/sleep.ts`).",
      "items": {
        "type": "string"
      },
      "type": "array"
    },
    "checkClientModules": {
      "description": "Also report the sleep form in browser/React Native modules. Off by default: those bundles cannot import `node:timers/promises` and the web platform has no equivalent, so the fix is impossible to follow. Turn on only where every file can resolve `node:` builtins.",
      "type": "boolean"
    }
  },
  "type": "object"
}