AbortSignal-like abstraction without event dispatch?

While reading the discussion in whatwg/dom#1461, I started wondering whether the underlying abstraction behind AbortSignal could be separated from its event-based aspects.

Related discussion:

  • whatwg/dom#1461 β€” "AbortSignal.timeout() may not abort past the timeout"

Motivation

AbortSignal today is fundamentally event-loop driven. In practice, this means that cancellation is not observable while the current agent is executing long-running synchronous work.

However, many use cases seem to only require synchronous observation of cancellation:

while (!signal.aborted) {
  performWork();
}

In such cases, what matters is not event delivery, but the ability to observe cancellation state (aborted, reason) and react to it (throwIfAborted()).

Observation

AbortSignal currently combines multiple concerns:

  1. Cancellation state

    • aborted
    • reason
  2. Cancellation observation

    • throwIfAborted()
  3. Notification mechanism

    • "abort" event via EventTarget

The issue above suggests that (1) and (2) are sometimes needed without (3), especially when event-loop progress is not guaranteed.

Question

Would it make sense to think about an "AbortSignal-like" abstraction that includes:

  • aborted
  • reason
  • throwIfAborted()
  • (possibly) composition helpers like any()

but does not require event dispatch?

In other words:

AbortSignal
  = AbortSignal-like (state + logic)
  + EventTarget (notification)

Possible implications

If such an abstraction existed, different implementations could be possible:

  • DOM AbortSignal (event-driven)
  • Timeout-based signals (clock-driven)
  • Shared-state signals (e.g. across agents, possibly via shared memory)
  • Userland implementations

In particular, some environments may want cancellation state to be observable independently of the event loop, while still optionally supporting event-based notifications.

Scope

I am not proposing changes to AbortSignal itself.

Rather, I am wondering:

  • Is this separation of concerns meaningful?
  • Would an "AbortSignal-like" abstraction make sense at the language level?
  • Or is this better left as platform-specific or userland patterns?

Curious to hear thoughts, especially if similar ideas have been explored before.

Related discussion about one possible implementation approach:

I have thought about this a great deal and written up most of it at GitHub - bakkot/structured-concurrency-for-js: Exploring adding structured concurrency to JavaScript Β· GitHub. See in particular this issue.

Short version: I don't think it's feasible to have this be a separate mechanism from AbortSignal, and I do think we need some way to actively respond to cancelation, but that doesn't mean we have to keep using the event machinery; indeed native consumers already bypass the event machinery when using AbortSignal. And I think this could be a general interface where you could implement your own AbortSignal-like thing; consumers in the language (although unfortunately possibly not the DOM) would consume it via a well-defined public interface which could be implemented by anything.

1 Like

This can already be handled without adding to the language. Since the OP isn't interested in events, event handlers don't have to be changed, and the only change needed for top-level code (outside of any function) is to call it using try/catch. It is even possible to place the try/catch inside of a function, if it is desired to allow for silent returns from any calling depth just by using one additional function call. Then you simply use 'throw()' anywhere you wish to terminate execution. Note that if asynchronous JavaScript is used, the try/catch must handle Promise errors correctly, so they can be used for terminating execution.