Cancelling the JavaScript sleep function

A typical JavaScript sleep implementation is this one-liner:

      const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))

    

In some cases you’ll need cancel this timeout mid-flight. One elegant solution is to accept an AbortSignal similar to other async APIs like fetch.

      function sleep(ms, signal) {
	return new Promise((resolve, reject) => {
		const onAbort = () => {
			clearTimeout(timeoutId)
			reject(new Error("Aborted"))
		}

		const timeoutId = setTimeout(() => {
			signal?.removeEventListener("abort", onAbort)
			resolve()
		}, ms)

		signal?.addEventListener("abort", onAbort)
	})
}

    
TypeScript version
      function sleep(ms: number, signal?: AbortSignal) {
	return new Promise<void>((resolve, reject) => {
		const onAbort = () => {
			clearTimeout(timeoutId)
			reject(new Error("Aborted"))
		}

		const timeoutId = setTimeout(() => {
			signal?.removeEventListener("abort", onAbort)
			resolve()
		}, ms)

		signal?.addEventListener("abort", onAbort)
	})
}

    

Now you can cancel the sleep timeout with AbortController.abort().

      const controller = new AbortController()

sleep(1000, controller.signal)
	.then(() => console.log("done"))
	.catch(() => console.log("aborted"))

// in some other handler...

controller.abort()

    

You could certainly return your own cancel() method from the sleep function along with the promise, but that would change the familiar function signature.

What I like about this abort signal approach is:

  • It’s unobtrusive—the signal argument is optional, and output is still a plain promise
  • You can use the same signal to cancel other async processes