runCancellable<T> function

Future<T> runCancellable<T>(
  1. CancellationToken token,
  2. Future<T> task()
)

Awaits task but stops waiting if token cancels first.

Returns the task's value on normal completion. If token cancels before the task completes, throws a CancellationException carrying the token's reason. Cooperative only: the task keeps running after a cancel — this helper merely stops awaiting it, so the task should also check token at safe points.

Example:

final CancellationToken token = CancellationToken();
final int value = await runCancellable(token, () async => 42);

Audited: 2026-06-12 11:26 EDT

Implementation

Future<T> runCancellable<T>(
  CancellationToken token,
  Future<T> Function() task,
) {
  final Completer<T> completer = Completer<T>();

  // Cancellation wins the race: surface the token's reason as an exception.
  // Capture the stack at registration so the async error chain points back here
  // rather than to the bare completeError call site.
  final StackTrace cancelStack = StackTrace.current;
  token.onCancel(() {
    if (!completer.isCompleted) {
      completer.completeError(CancellationException(token.reason), cancelStack);
    }
  });

  // Task completion wins the race: forward its value or error if still pending.
  unawaited(
    task().then(
      (T value) {
        if (!completer.isCompleted) {
          completer.complete(value);
        }
      },
      onError: (Object error, StackTrace stackTrace) {
        if (!completer.isCompleted) {
          completer.completeError(error, stackTrace);
        }
      },
    ),
  );

  return completer.future;
}