Skip to main content
Version: june-2022

Cancellation Scopes

In the TypeScript SDK, Workflows are represented internally by a tree of Cancellation Scopes, each with cancellation behaviors you can specify. Everything runs in the "root" scope by default.

Scopes are created using the CancellationScope constructor, or one of 3 static helpers:

  • cancellable(fn): children are automatically cancelled when their containing scope is cancelled.
    • Equivalent to new CancellationScope().run(fn).
  • nonCancellable(fn): prevents cancellation from propagating to children.
    • Equivalent to new CancellationScope({ cancellable: false }).run(fn).
  • withTimeout(timeoutMs, fn): if timeout triggers before fn resolves the scope will be cancelled, triggering cancellation of enclosed operations, such as activities and timers.
    • Equivalent to new CancellationScope({ cancellable: true, timeout: timeoutMs }).run(fn).

Cancellations are applied to cancellation scopes, which can encompass an entire workflow or just part of one. Scopes can be nested, and cancellation propagates from outer scopes to inner ones. A Workflow's main function runs in the outermost scope. Cancellations are handled by catching CancelledFailures thrown by cancellable operations (see below).

CancellationScope.run() and the static helpers mentioned above all return native JS Promises, so you can use the familiar Promise APIs like Promise.all and Promise.race to model your async logic. Other APIs you can use:

  • CancellationScope.current(): get the current scope
  • scope.cancel(): cancel all operations inside a scope
  • scope.run(fn): run an async function within a scope, returns the result of fn
  • scope.cancelRequested: a promise that resolves when a scope cancellation is requested, e.g. when Workflow code calls cancel() or the entire Workflow is cancelled by an external client.

When a CancellationScope is cancelled, it propagates cancellation in any child scopes and of any cancellable operations created within it, such as:

CancelledFailure

Timers and Triggers throw CancelledFailure when cancelled while Activities and Child Workflows throw ActivityFailure and ChildWorkflowFailure with cause set to CancelledFailure. One exception is when an Activity or Child Workflow is scheduled in an already cancelled scope (or workflow) in which case they'll propagate the CancelledFailure that was thrown to cancel the scope.

In order to simplify checking for cancellation, use the isCancellation(err) function.

Internal cancellation example

Alternatively, the preceding can be written as:

External cancellation example

Handle Workflow cancellation by an external client while an Activity is running:

nonCancellable example

CancellationScope.nonCancellable prevents cancellation from propagating to children:

withTimeout example

A very common operation is to cancel one or more activities if a deadline elapses, withTimeout creates a CancellationScope that is automatically cancelled after a given timeout.

scope.cancelRequested

You can await cancelRequested to make Workflow aware of cancellation while waiting on nonCancellable scopes:

CancellationScopes and callbacks

Callbacks are not particularly useful in Workflows because all meaningful asynchronous operations return Promises. In the rare case that user code utilizes callbacks and needs to handle cancellation, a callback can be used to consume the CancellationScope.cancelRequested Promise.

Nesting Cancellation Scopes

Complex flows may be achieved by nesting cancellation scopes:

Sharing promises between scopes

Operations like timers and Activities are cancelled by the cancellation scope they were created in. Promises returned by these operations can be awaited in different scopes.