join<T> method

  1. @override
Future<T> join<T>(
  1. FutureOr<T> action(), {
  2. FutureOr<void> dispose(
    1. T value
    )?,
  3. FutureOr<void> discard(
    1. T value
    )?,
})
override

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;
}