Перейти к содержимому

Исходы

Когда тело, дочерние задачи и уборка завершились, await job.done возвращает Outcome<T>. Это ожидание никогда не бросает исключений: успех, ошибка и отмена представлены вариантами Done, Failed и Cancelled. Outcome<T> объявлен как sealed, поэтому switch по этим вариантам исчерпывающий:

final message = switch (await job.done) {
Done(:final value) => 'done $value',
Failed(:final error) => 'failed $error',
Cancelled(:final reason) => 'cancelled $reason',
};

Если нужно только возвращённое значение, используйте await job.value. При успехе он вернёт значение, а при ошибке или отмене бросит исключение. Если результат совсем не нужен, подтвердите это вызовом job.ignore().

Обращение к done или value, а также вызов ignore() считаются наблюдением ошибки. Передача провала через then тоже наблюдает его: ответственность переходит к продолжению. Чтение job.outcome, получение колбэка наблюдателя onFinish и ожидание job.cancel() не считаются. Ненаблюдаемая ошибка передаётся в зону создания задачи на микротаске после её завершения. Так ошибка остаётся видна, даже когда вызывающий код не ждёт результата.

При отмене исход также объясняет, почему работа остановилась. Cancelled содержит причину reason, флаг started, необязательное описание description и стектрейс отмены. Флаг started показывает, успело ли тело запуститься или отмена произошла до запуска. Встроенные классы причин: ManualCancelReason, ParentCancelReason, HandlerCancelReason и ChainCancelReason. Все они наследуют CancelReason.

Проверяйте причину по типу, например reason is ParentCancelReason. Свойство name служит меткой для журнала и не определяет равенство. По умолчанию причины сравниваются по идентичности, но класс может определить равенство по данным.

В собственной причине можно хранить дополнительные данные, например ошибку и её исходный стектрейс:

final class RequestCancelReason extends CancelReason {
final Object error;
final StackTrace stackTrace;
const RequestCancelReason(this.error, this.stackTrace);
@override
String get name => 'request';
}

Передайте причину в cancel(). Обработчик отмены и исход получат тот же экземпляр:

try {
await request();
} on Object catch (error, stackTrace) {
await job.cancel(reason: RequestCancelReason(error, stackTrace));
}

Тело может бросить Cancelled.by(reason: reason, started: true) с явной причиной или Cancelled('why') с причиной HandlerCancelReason. Когда родитель отменяет дочернюю задачу, её ParentCancelReason.cause хранит Cancelled родителя. Когда отмена дочерней задачи выходит через тело родителя, его HandlerCancelReason.cause хранит Cancelled дочерней задачи. Эти ссылки сохраняют исходную причину и её данные. Стектрейс ошибки, сохранённый в причине, отделён от стектрейса отмены.

Чтобы отреагировать на принятую отмену до получения итогового исхода, зарегистрируйте обработчик через job.whenCancelled(callback). Он вызывается синхронно и получает Cancelled с причиной и подробностями. Сам метод регистрации возвращает функцию снятия обработчика. Момент вызова зависит от способа отмены:

  • При внешней отмене работающей задачи он вызывается после передачи отмены дочерним задачам и вызова колбэков ctx.onCancel, не дожидаясь окончания тела.
  • При отмене до запуска он вызывается в момент отмены задачи.
  • Если тело бросило Cancelled, он вызывается после окончания тела и дочерних задач, перед уборкой.
final job = Job<Report>(build);
// Отмена принята; задача ещё может завершать работу.
final unregister = job.whenCancelled((cancelled) {
print('cancelling: ${cancelled.reason}');
});
final outcome = await job.done;
// Безопасно после завершения; можно снять раньше, если больше не слушаем.
unregister();

При регистрации после отмены обработчик вызывается сразу, даже если задача уже завершилась. Отклонённая отмена не вызывает обработчиков. Секция uncancellable откладывает уведомление до принятия отмены. Задача, завершившаяся с Done или Failed без отмены, освобождает обработчики без вызова. Регистрация обработчика не считается наблюдением ошибки.

Каждая регистрация срабатывает один раз. Обработчики вызываются в порядке регистрации, по снимку списка: снятие обработчика во время уведомления не исключает его из текущего прохода. Добавленный в это время обработчик вызывается сразу. Повторное снятие регистрации безопасно.

Синхронная ошибка обработчика передаётся в onError, а без наблюдателя в зону создания задачи. Брошенный Cancelled в зону не передаётся. Ошибки обработчиков не меняют отмену и не мешают вызову остальных. Если передать async-колбэк, его future не будет ожидаться, а её ошибки не будут перехвачены этим механизмом.