runCancellable<T> function
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;
}