Source

CANCELLATION PRIMITIVES
Ron Buckton - Microsoft Corporation

Clear and consistent approach to the cancellation of
asynchronous operations

General purpose coordination primitive

Host API integration (fetch, module loading, etc.)
MOTIVATIONS

Separation of source and sink

Cancellation request can be observed synchronously, via
property

Cancellation request can be observed asynchronously, via
callback

Able to unregister callbacks

Composable for non-trivial cancellation graphs
GOALS
Source
Sink

Owned by caller

Received by callee

Can share entangled Sink with
multiple callees

Can observe cancellation state

Cannot initiate cancellation signal
on Source

Initiates cancellation signal

SOURCE/SINK
Callee A cannot interfere with
Callee B using the same Sink
Synchronous
Asynchronous

Observe cancellation request via
property

Observe cancellation request via
callback

Useful for loops and multi-phase
operations in async functions

Able to unregister/unsubscribe from
cancellation



Game Loops
Async Iteration
Best for async functions
OBSERVABILITY


Allows GC of closures

Stop listening when callback is no
longer needed
Best for non-async functions that
return Promise
Parent/Child
Root
Aggregation
Child1
Source1
Child2
Source2
Child3
Source3
COMPOSABLE
Aggregate

Promise as Token

AbortController (WHATWG)

CancellationToken (TC39)
CURRENT PROPOSALS
function asyncfn(token) {
token.then(() => { /*cancelled*/ });
}
let cancel, token = new Promise(resolve => cancel = resolve);
asyncfn(token);
cancel();
PROMISE AS TOKEN

Uses existing API

Cannot be observed synchronously

Can be observed asynchronously

Cannot be unregistered


Must use additional logic to prevent execution

Closure persists for lifetime of source
Not easily composable
PROMISE AS TOKEN
function asyncfn(signal) {
const onabort = () => { /*cancelled*/ };
signal.addEventListener('abort', onabort);
signal.removeEventListener('abort', onabort);
if (signal.aborted) { /*cancelled */ }
}
const controller = new AbortController();
asyncfn(controller.signal);
controller.abort();
ABORTCONTROLLER (WHATWG)

New API: https://github.com/whatwg/dom/pull/437

Can be observed synchronously

Can be observed asynchronously


Takes dependency on DOM events
Callbacks can be removed (via removeEventListener)
ABORTCONTROLLER (WHATWG)
function asyncfn(token) {
const reg = token.register(() => { /*cancelled*/ });
reg.unregister();
if (token.cancellationRequested) { /*cancelled*/ }
}
const source = new CancellationTokenSource();
asyncfn(source.token);
source.cancel();
CANCELLATIONTOKEN

New API: https://github.com/tc39/proposal-cancellation

Can be observed synchronously

Can be observed asynchronously

Callbacks can be removed (via unregister)

Easily composable:
async function asyncfn(token1, token2) {
const source = new CancellationTokenSource([token1, token2]);
setTimeout(() => source.cancel(), 500);
await otherfn(source.token);
}
CANCELLATIONTOKEN

CancellationTokenSource

new CancellationTokenSource([linkedTokens])

source.token

source.cancel()

source.close()
CANCELLATIONTOKEN API

CancellationToken

CancellationToken.none

CancellationToken.canceled

token.cancellationRequested

token.canBeCanceled

token.throwIfCancellationRequested()

token.register(callback)

registration.unregister()
CANCELLATIONTOKEN API


Promise

new Promise(executor, token)

promise.then(onfulfill, onreject, token)
Observable


observable.subscribe(observer, token)
Dynamic import

import(“module”, token)
POSSIBLE FUTURE INTEROP

Stage 0

Identified Champions: Ron Buckton, Brian Terlson

Strawman available


Early Spec Proposal available


https://tc39.github.io/proposal-cancellation/
Prototype available


https://github.com/tc39/proposal-cancellation
https://github.com/rbuckton/prex
Requesting Stage 1
STATUS