execute method
Execute a closure either async or sync. The Executor
is only called if
all deferred cancels return false and only after all executionDeferrals
have completed. If exec
returns a future, its result is piped through to
the onDone
future, otherwise, the onDone
future is completed with the
result.
Implementation
Future<void> execute(dynamic Function() exec,
{dynamic Function()? onCancel, V? valueOnCancel}) {
// This function is very time-sensitive.
// We are using explicit `Future`s to avoid breaking changes when the
// behavior of `async` changes.
// TODO(google): switch back to `async` with an explicit `await null;` at
// the beginning of the function, once the migration is done.
return Future.microtask(() {
if (_locked) {
throw StateError('Cannot execute, execution already in process.');
}
_locked = true;
// Check for cancellations
return _shouldCancel().then((shouldCancel) {
_cancelled = shouldCancel;
var shouldProceed = !_cancelled;
_deferCompleter.complete(shouldProceed);
if (shouldProceed) {
// Wait for any execution deferrals.
return _maybeWait().then((_) {
_executeAndAttach(exec);
});
} else {
_done = true;
// FutureCancellations have cancelled this action. Run the onCancel,
// then complete with [valueOnCancel].
if (onCancel == null) {
_executeCompleter.complete(valueOnCancel);
} else {
var cancelRes = onCancel();
if (cancelRes is! Future) {
_executeCompleter.complete(valueOnCancel);
} else {
// The action should resolve [onDone] with [valueOnCancel] if
// canceled, so, while we need to await the cancel result, we want
// to throw it away.
_attachFuture(cancelRes.then(((_) => valueOnCancel!)));
}
}
return null;
}
});
});
}