Skip to content

feat(promise): add AbortSignal support to timeout and withTimeout - #1617

Merged
raon0211 merged 4 commits into
toss:mainfrom
MyraxByte:feat/abort-timeout
Jun 20, 2026
Merged

raon0211 merged 4 commits into
toss:mainfrom
MyraxByte:feat/abort-timeout

Conversation

@MyraxByte

Copy link
Copy Markdown
Contributor

Description

This PR adds AbortSignal support to the timeout and withTimeout utilities in es-toolkit so that timeouts can be cancelled via AbortController.

Changes

  • Extend timeout(ms) to accept an optional AbortSignal via TimeoutOptions and pass it through to delay
  • Extend withTimeout(run, ms) to accept an optional AbortSignal via WithTimeoutOptions and pass it through to timeout
  • Update JSDoc examples to demonstrate usage with AbortController and aborted operations
  • Add tests to cover:
    • Rejecting with AbortError when the operation is aborted via AbortSignal
    • Immediate rejection when the signal is already aborted

Motivation

Some consumers need to cancel pending timeouts when the surrounding operation is cancelled (for example, when an HTTP request is aborted). By wiring AbortSignal support through timeout and withTimeout, we enable safer cancellation semantics and better resource cleanup without changing existing call sites.

Breaking Changes

None. Existing calls without options continue to work as before, and Abort-related behavior is opt‑in via the signal option.

@vercel

vercel Bot commented Feb 14, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
es-toolkit Ready Ready Preview, Comment Jun 20, 2026 6:32am

Request Review

@raon0211

raon0211 commented Jun 20, 2026 •

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

The approach here wires signal through delay, so aborting makes timeout reject with AbortError. The problem is that this inverts what cancelling a timeout should mean. timeout only exists to lose a Promise.race, so aborting it should stop it from firing, not make it reject faster. And in withTimeout the race rejects while run keeps running, because the signal never reaches run, so nothing is actually freed. The cause is reusing delay's reject-on-abort, which is right for delay (it is awaited directly) but wrong for timeout (it is only raced).

So I pushed a commit that changes it: on abort, timeout now clears its timer and stays pending, and withTimeout forwards the signal so aborting lifts the time limit. This does not leak, since the timer and listener are cleaned up and the promise is GC'd once the race settles. I also added the docs in all four languages.

Thanks again for contributing, and please keep it coming next time too!

timeout() exists only to lose a Promise.race, so aborting it should stop it
from firing (stay pending) rather than reject with AbortError. Delegating to
delay() inherited delay's reject-on-abort, which is right for delay (awaited
directly) but inverted for timeout: it made the timeout fire earlier and
rejected withTimeout while run() kept running.

Now timeout clears its own timer and stays pending on abort, and withTimeout
forwards the signal so aborting lifts the time limit. The timer and abort
listener are cleaned up, so the pending promise is garbage collected.

Also documents the signal option for timeout and withTimeout in all four
languages.
@raon0211
raon0211 force-pushed the feat/abort-timeout branch from c64cc88 to 53e9358 Compare June 20, 2026 06:20
raon0211
raon0211 previously approved these changes Jun 20, 2026
# Conflicts:
#	src/promise/timeout.ts
#	src/promise/withTimeout.ts
@raon0211
raon0211 merged commit f0724b6 into toss:main Jun 20, 2026
10 checks passed

This branch was successfully deployed

1 active deployment
Preview — 5e0f6f55 Deployed Jun 20, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants