join<T> method
Runs action, waits for all of it, and gives up only afterwards.
For a call that must not be left in flight: a device command, a write
already on the wire. A cancellation arriving while action runs is
accepted at once, but the waiting is not cut short: when action comes
back, this throws that Cancelled, and if none arrived, it returns
action's value. Throws Cancelled up front if the job is already
cancelled or its rules no longer hold. An error from action is thrown
as it is, cancelled or not — so await this call: a body that walked
on lets that error reach a future nobody awaits, and Dart hands it to the
zone, past the job's observer.
await ctx.join(() => device.write(chunk));
The value is dropped when the job gives up, unless dispose or discard
takes it. A value that did not reach the body is released on the spot,
and here that is awaited before the Cancelled is thrown, so the next job
starts with the resource gone; one that did goes on the cleanup stack, as
in abandonable. Passing both is an ArgumentError. The checkpoint after
action releases the value whatever it throws, a rule of a domain that
throws included. An error from the disposer goes to onError and on to
the job's answer, and the Cancelled is thrown all the same.
See A token through onCancel
on the cancellation page.
Implementation
@override
Future<T> join<T>(
FutureOr<T> Function() action, {
FutureOr<void> Function(T value)? dispose,
FutureOr<void> Function(T value)? discard,
}) async {
throwIfFinished('join');
_oneCleanupOnly(dispose, discard);
check();
final result = await action();
if (_owner.bodyEnded && _inOwnFork) {
// Called from work handed over with `unattended`, and that work is
// waiting for the value: it is the receiver, on the body's terms. A job
// marked cancelled -- from outside, or by a body that cancelled itself --
// releases the value and throws the cancellation, the rules of a domain
// aside: they speak to the body, which is gone. Otherwise the value is
// the work's on the terms the call asked for, and on a job that is over
// it is released and the call throws, as for a job an engine finished by
// hand.
try {
throwIfCancelled();
} on Cancelled {
final disposer = dispose ?? discard;
if (disposer != null) {
await _dispose(disposer, result);
}
rethrow;
}
_keepOnStack(dispose, discard, result);
return result;
}
if (_owner.bodyEnded) {
// The body ended while the call was in flight: the value did not
// reach it, and nothing is waiting for this call any more. It hands
// the value to its cleanup and ends quietly — neither `check` nor
// the job's cancellation goes in, because either would land in a
// future nobody awaits and Dart would hand it to the zone.
await _keepLate(dispose, discard, result);
return result;
}
try {
check();
} on Object {
// Whatever the checkpoint threw, the value stops here: it reaches no
// body, and the stack below is never reached, so this is its only
// way out. `on Object`, not `on Cancelled`: a domain checks its own
// rules in here, and a rule that threw instead of answering must not
// cost the caller the resource it already holds.
final disposer = dispose ?? discard;
if (disposer != null) {
await _dispose(disposer, result);
}
rethrow;
}
_keepOnStack(dispose, discard, result);
return result;
}