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

Список изменений

  • doc/reference.md отправляет за таблицей контекста в справочник async_job, куда таблица переехала из его README.
  • pubspec.yaml называет сайт документации, и pub.dev ссылается на него.
  • README и doc/jobs.md больше не говорят, что enqueue без аргументов типа работает в теле со всем S: там ctx.state имеет тип Object?. README теперь говорит и о том, что рабочий тип действует от старта задачи до её конца.
  • Новая страница doc/riverpod.md показывает контроллер под провайдером riverpod: мост между ними, камеру, которую закрывают при уходе экрана, форму, которую отправляют один раз, и то, чего контроллер там стоит.
  • Новая страница doc/bloc.md ставит контроллер под bloc: мосты для его задач и его состояний и их цену, три требования, где контроллер снимает с bloc обходной приём, и сценарии, где ответ остаётся за bloc.

Этот пакет реэкспортирует async_job целиком, поэтому изменения ядра принадлежат и этому пакету: см. список изменений async_job.

  • ctx.wait из async_job, устаревший с 0.3.0 в пользу ctx.abandonable, удалён.
  • Policy.replace переименован в Policy.keepLatest. Имя говорит, что остаётся в очереди: последняя из ждущих задач с этим ключом. Снятая задача по-прежнему кончается Cancelled(replaced) с ReplacedCancelReason, а AccumulationPolicy.replace сохраняет своё имя.
  • HandlerCancelReason из async_job переименован в BodyCancelReason, а его name теперь body, а не handler.
  • SoloContext.each возвращает SoloJob<void>, как и SoloContext.child. Класс, который сам реализует SoloContext, тоже возвращает этот тип.
  • Solo.debug печатает drop с задачей и её Cancelled, когда правила рабочего типа отклоняют задачу из очереди на старте.
  • README говорит, в каком выпуске с версии 1.0.0 выходит каждое изменение: исправление в патч-выпуске, добавление в минорном, ломающее в мажорном, и что solo выходит с каждым минорным выпуском async_job, на протоколе движка которого он построен.
  • README называет плагин анализатора solo_lints в разделе «Установка», а страница частых ошибок говорит, что о большинстве из них плагин сообщает.
  • Страница диагностики говорит, что печатает её наблюдатель и что наблюдатель, чей лог уходит с устройства, печатает только то, что приложение решило отдать.
  • Быстрый старт README помещается на один экран: контроллер, загрузка и обновление, отменённое до ответа, с тем, что печатает программа; example/bin/quick_start.dart его запускает. Контроллер с ключом, политикой, обработчиками состояния и ожидаемыми ошибками, на котором строятся руководства, открывает страницу о задачах как раздел «Контроллер профиля».
  • Страница о задачах заканчивается разделом «Нажатие, которое не ждёт перезагрузки»: лайки ленты живут в своём контроллере, и лайк не ждёт за перезагрузкой ленты.
  • README говорит, что новое значение перечисления или новый подкласс запечатанного класса выходит в мажорном выпуске, называет перечисления и запечатанные классы пакета и говорит, для кого package:solo/listeners.dart.
  • README говорит, что задача не Future и что задача, которую правила отклонили на старте, кончается Cancelled с RulesCancelReason, а не Failed.
  • Справочник называет cancelAll с force и reason, поля SoloPending, SoloPhase и SoloUnansweredHandler.
  • Страницы об отмене, ресурсах и ошибках дают весь путь ошибки, за которую никто не ответил: onUnanswered, затем Solo.unansweredHandler или зона. Страница ошибок называет среди правил старта и keepWhile, спрошенный на старте, и говорит, куда ошибка идёт дальше во Flutter-приложении.
  • Страницы о задачах, стримах, дочерних задачах, накоплении, камере и частых ошибках говорят то, что нужно читателю примеров: что queue, lastJobWhere и current защищены, что ctx.each возвращает SoloJob<void>, что такое _Op, почему накопитель живёт в поле late final, какая строка камеры отменяет задачу и что catch, который публикует состояние сбоя, на отменённой задаче ничего не даёт.
  • Страница об отмене говорит, что секция uncancellable держит и срок и что задача с cancellable: false не принимает timeout: вызов, который передаёт оба, бросает ArgumentError.
  • Нижняя граница SDK — Dart 3.10.0, прежде была 3.6.0.
  • Solo.job, Solo.enqueue, Solo.collect и Solo.accumulate принимают именованный isExpectedError. Подкласс, который переопределяет один из них, обязан его принять.
  • isExpectedError называет ошибки, которых задача ждёт: onError слышит их с JobErrorAnswer.never, отвечать за них никого не спрашивают, а баг по-прежнему идёт к ответчикам и в зону. См. «Назвать ожидаемые ошибки».
  • Solo.onError и SoloObserver.onError принимают {required JobErrorAnswer answer}, как JobObserver.onError ядра. См. «Отправка ошибки».
  • У SoloContext появился child, ctx.child ядра: он делает задачу того же контроллера с рабочим типом и keepWhile той задачи, которая его сделала. Код, у которого есть только JobContext, делает им ребёнка, которого контроллер примет, а Job.deferred контроллер отвергает. Класс, который сам реализует SoloContext, его добавляет. См. «Дочерние задачи и цепочки» и список изменений async_job.

Этот пакет реэкспортирует async_job целиком, поэтому изменения ядра принадлежат и этому пакету: см. список изменений async_job.

Этот пакет реэкспортирует async_job целиком, поэтому изменения ядра принадлежат и этому пакету: см. список изменений async_job.

Что писать вместо прежнего, слева имя в 0.2.0; у каждого есть своя запись ниже.

0.2.0 0.3.0
SoloBase<S> Solo<S>
Solo<S>, класс со stream Solo<S> with SoloStream
controller.state controller.currentState
onError: у run и job ifFailed:
onCancel: у run и job ifCancelled:
run(body) enqueue(body)
add(job) enqueueJob(job)
Job<T> помощника, переданный в ctx.run SoloJob<T>
onChange(previous, current) onChange(transition)
SoloObserver.onChange(solo, previous, current) SoloObserver.onChange(solo, transition)
SoloQueue.lastWhere Solo.lastJobWhere
Job.ignore() Job.ignoreFailure()
JobBase.debug Job.debug
  • Solo.onError стал уведомлением, а за ошибку, которой больше ни у кого нет, отвечает новый Solo.onUnanswered: за ту, которой не несёт ни один исход, и за Failed, который никто не наблюдает. Чтобы такая ошибка не дошла до зоны, задайте Solo.unansweredHandler, это SoloUnansweredHandler, или переопределите onUnanswered: заданный Solo.observer или переопределение onError, которое не зовёт super, больше этого не делают. Failed, который никто не наблюдает, уходит к обработчику или к переопределённому onUnanswered через микрозадачу после конца задачи, а до зоны доходит, как в 0.2.0, только когда не задано ни того, ни другого. Так же уходит и провал бросившего canStart. См. «Ответ за ошибку».
  • Job.ignore() переименован в Job.ignoreFailure() в async_job.
  • JobBase.debug теперь называется Job.debug в async_job, чья главная библиотека больше не экспортирует JobBase.
  • Задача продолжается один раз, одним then или ручками shared(), в async_job: второй then на задаче из enqueue бросает StateError. Задача, которую Policy.droppable может отдать другому вызывающему, и задача группы накопителя отказывают простому then с первого вызова: каждый вызывающий продолжает её через ручку shared(). См. «Очередь и политики» и «Запуск, отмена и ошибки».
  • Обработчики состояния у enqueue и job переименованы: onError в ifFailed, onCancel в ifCancelled.
  • SoloBase переименован в Solo, а прежний Solo, класс, который нёс broadcast-стрим, стал миксином SoloStream. Контроллер, у которого читают .stream, добавляет with SoloStream. См. «Наблюдение за состоянием».
  • Solo.run переименован в enqueue, а Solo.add — в enqueueJob. run контроллера ставил задачу в хвост очереди, а ctx.run запускает ребёнка сразу, мимо очереди; add делил имя с add накопителя, который принимает событие. См. «Создание и запуск задач».
  • job, enqueueJob, enqueue, collect и accumulate помечены @protected. Дайте контроллеру метод на каждую операцию, которую вызывают снаружи. См. «Создание и запуск задач».
  • SoloStream.publish помечен @protected.
  • Хуки onStart, onFinish, onError, onUnanswered, onLog, onChange и onClose помечены @protected.
  • SoloJob — это ChildJob, тип, который теперь принимают ctx.run и ctx.runAll из async_job. Задача из job(...), которую держат как Job, там больше не компилируется: помощнику, который её делает, дайте тип SoloJob, как SoloJob<void> _recordPath(String path) => job(...). Продолжение из then, которому ctx.run отказывал с ArgumentError, там тоже больше не компилируется. См. «Цепочки завершённой работы».
  • Синхронное чтение контроллера называется currentState, а не state. Тело читает ctx.state. См. «Почему currentState, а не state».
  • Состояние закрытого контроллера окончательно. Когда движок закончил закрытие, externalSetState бросает StateError: защитой служит новый Solo.isFinished, а последним местом, где запись проходит, стал новый хук Solo.onClose. См. «externalSetState».
  • Solo.close принимает SoloCloseMode. По умолчанию стоит SoloCloseMode.cancel, то есть то, что close делал всегда; SoloCloseMode.drain выполняет то, что уже стоит в очереди, а не отбрасывает. См. «Три способа остановиться».
  • Хуки изменения принимают SoloTransition<S> вместо пары состояний: Solo.onChange(transition) и SoloObserver.onChange(solo, transition).
  • Подкласс с членом, названным как новый член Solo, перестаёт собираться или переопределяет его. Новые члены экземпляра: currentState, isFinished, isDraining, pending, enqueue, enqueueJob, onClose, onUnanswered, addListener, removeListener, hasListeners и onListenerError.
  • Класс, который сам реализует SoloContext, добавляет abandonable, heldCancellation, pause, runAll и start, новые члены JobContext в async_job, его run принимает dispose и discard, а each — key и describe.
  • Policy.droppable отказывает ключу, который держит задача с другим типом результата, с ArgumentError, и отказывает до того, как примет новую задачу. См. «Ключ на каждый запрос».
  • Задача, отброшенная Policy.droppable, заканчивается с новой причиной DuplicateCancelReason, а не с ManualCancelReason.
  • Задача, на место которой Policy.replace или Policy.restart ставит более новую, заканчивается с новой причиной ReplacedCancelReason, а не с ManualCancelReason.
  • collect и accumulate по умолчанию берут AccumulationPolicy.join, а не adjacent. Задача другого рода, вставшая в очередь между двумя событиями, больше не граница; назовите adjacent там, где граница нужна. См. «Команды, где важна только последняя».
  • AccumulationPolicy.replace переносит задачу ждущей группы в хвост, вместо того чтобы строить новую задачу и отменять старую. Каждое событие одной группы теперь делит её хэндл и исход при любой политике.
  • SoloQueue.lastWhere убран. Solo.lastJobWhere находит ту же задачу и смотрит ещё и на идущую.
  • cancelAll и SoloQueue.remove, removeWhere и clear принимают reason.
  • SoloQueue.jobs и Solo.current отдают SoloJob<Object?>, и его же принимают предикаты SoloQueue.removeWhere и Solo.lastJobWhere. Предикат, написанный для Job<Object?>, собирается как прежде; переопределение current или lastJobWhere и класс, который реализует SoloQueue, берут новые типы.
  • job, enqueue, collect и accumulate принимают timeout. Переопределение передаёт его дальше в super, иначе enqueue(timeout: ...) молча остаётся без срока.
  • collect и accumulate принимают ifFailed и ifCancelled. Переопределение передаёт их дальше в super, иначе группы его накопителей молча остаются без обработчиков состояния.
  • ctx.wait в async_job устарел, его заменяет ctx.abandonable; старое имя уйдёт в следующем выпуске.

Ошибки, которые 0.2.0 теряла или оставляла хукам, теперь доходят до Solo.unansweredHandler или, когда обработчик не задан, до зоны, где создана задача. В тесте это зона теста, и тест падает:

  • ошибка, которой не несёт ни один исход, в приложении, которое задаёт Solo.observer или переопределяет onError, не зовя super;
  • провал тела задачи, которая кончилась Cancelled: операция, которая останавливается на токене, отменяемом задачей через ctx.onCancel, бросает такой провал при каждой отмене. Job.isCancellationError один раз, в main, называет ошибки, которые вызывает отмена, и они доходят до onError и дальше не идут; job.ignoreFailure() глушит провал только своей задачи;
  • поздняя ошибка действия, которого тело не дождалось из-за .timeout или Future.any, пришедшая, когда задача уже кончилась;
  • провал собственной уборки источника, когда ctx.each отпускает стрим.

Задача, которая сдаётся через ctx.check() внутри вызова, который ctx.abandonable уже отпустил, больше не доходит до onError как провал.

onError контроллера и наблюдателя слышит каждый провал и каждый Cancelled внутри ParallelWaitError отдельным вызовом, а 0.2.0 отдавала ему всю ошибку один раз. Failed с такой ошибкой, который никто не наблюдает, доходит до Solo.unansweredHandler или до зоны по провалу и без своих отмен, а 0.2.0 отдавала зоне всю ошибку. См. «Ответ за ошибку».

Отброшенный дубликат печатает Cancelled(duplicate), задача, заменённая под Policy.replace или Policy.restart, печатает Cancelled(replaced), а несколько сообщений об ошибках сформулированы иначе.

Стектрейс задачи, отменённой её правилами, снимается там, где правила это заметили.

ctx.each спрашивает правила задачи до того, как запустить ребёнка, как это делает ctx.state. Если задача сама опубликовала через emit состояние, которое они отклоняют, ctx.each заканчивает её отменой по правилам, и ifCancelled не вызывается, как на любой контрольной точке правил. 0.2.0 в этом случае возвращала ребёнка, которого те же правила не дали запустить: стрим никто не слушал, задача, не прочитавшая ребёнка, кончалась Done, а прочитавшая его .value кончалась отменой от ребёнка, и ifCancelled вызывался. См. «Обработка стрима».

Тело задачи из очереди работает в зоне, где задачу создали, и с ним её срок и хуки onStart. 0.2.0 запускала их в зоне того, что разбудило очередь, чаще всего чужого вызова, и тело, ждавшее future своей зоны, зависало, если эта future падала, а само тело работало под другим runZonedGuarded. См. «Создание и запуск задач».

keepWhile не может записать состояние: запись, пока он отвечает, бросает StateError, и правило бросает вместе с ней, как любое бросившее правило. 0.2.0 пропускала запись и снова спрашивала правило о новом состоянии, и правило, которое писало при каждом вопросе, исчерпывало стек. canStart по-прежнему может писать: его спрашивают один раз.

Обработчик ifFailed или ifCancelled, который возвращает полученное состояние или const-значение, уже лежащее в состоянии, ничего не публикует и оставляет на месте собственную запись; 0.2.0 публиковала это состояние ещё раз как переход.

Строки Solo.debug о том, что задача встала в очередь, начинаются с enqueue, а не с add.

  • ctx.start из async_job запускает ребёнка, которого тело не ждёт. Правила контроллера решают так же, как для ctx.run: ребёнок, которому они отказали, кончается Cancelled(rules), а start возвращается как обычно; бросивший canStart бросает из start. Провал ребёнка уходит в onUnanswered.
  • timeout даёт задаче контроллера собственный срок. Его принимают job, enqueue, collect и accumulate; когда срок выходит, задача заканчивается Cancelled(timeout) с новой причиной TimeoutCancelReason из async_job. См. «Срок задачи».
  • collect и accumulate принимают ifFailed и ifCancelled, обработчики состояния job. Группа, которая написала собственное состояние, пока работала, откатывает его через них, когда падает или её отменяют; она вызывает один из них не больше одного раза, сколько бы событий в ней ни было. См. «Запуск, отмена и ошибки».
  • Job.isCancellationError из async_job называет ошибки, которые вызывает отмена. Провал отменённой задачи, который он называет, доходит до onError и дальше не идёт: его не слышат ни onUnanswered, ни Solo.unansweredHandler. См. «Токен».
  • Job.whenFinished из async_job слышит конец задачи, не наблюдая её исход. Слушатель получает исход, а за провал, который никто не наблюдал, всё равно отвечают. См. «Задачи и результаты».
  • ctx.each контроллера принимает key и describe из async_job, чтобы назвать своего ребёнка. key здесь только имя: политики очереди правят корневыми задачами, а ребёнок не корневой.
  • SoloObserver.all делает из нескольких наблюдателей одного для Solo.observer. См. «Отмена, которая не доходит».
  • Solo несёт собственных слушателей: addListener и removeListener, а рядом с ними защищённые hasListeners и onListenerError. См. «Наблюдение за состоянием».
  • Solo.pending говорит, что удерживает контроллер, когда close не вернулся. Это SoloPending или null, когда ничто не удерживает, а SoloPending бывает одним из трёх: SoloPendingJob со своей SoloPhase, SoloPendingQueue или SoloPendingStream. См. «Что удерживает контроллер».
  • AccumulationTiming.throttle принимает startAtOnce. С startAtOnce: false интервал отсчитывается и перед первой группой.
  • У AccumulationTiming есть ==, hashCode и toString, который печатает вид, длительность и startAtOnce в форме вызова конструктора.
  • package:solo/listeners.dart: Listeners, список слушателей, по которому идёт уведомитель, для пакета, который строит собственную доставку поверх solo.
  • Solo.debug называет идущую задачу в строке задачи, вставшей в очередь, пока та идёт: while Job(outer) is running: it starts after Job(outer) ends; так же говорит строка дубликата, который возвращает Policy.droppable, а дубликат идущей задачи кончается словами which is running: if awaited from inside Job(x), it waits for itself.
  • Состояние, которое canStart записал, пока отвечал, проверяется на W и keepWhile: задача стартовала под ним, даже когда оно им уже не подходило.
  • Policy.replace и Policy.restart ставят новую задачу в очередь раньше, чем отменяют задачи, которые она замещает. Задача того же ключа, которую onFinish или whenCancelled поставили, когда замещённая задача кончалась, вставала раньше новой, и выполнялись обе: последний запрос проигрывал предыдущему.
  • Ребёнок, которого собственный canStart отменил, отвечая «да», доходит до родителя своей отменой, как при отказе: родитель кончался Failed со StateError о том, что ребёнок уже закончился.
  • Ребёнку, которого собственное правило старта запускает ещё раз, отказывают с StateError: родитель переставал ждать ребёнка, тело которого продолжало работать, а onDispose ребёнка не вызывался.
  • Policy.droppable и Solo.lastJobWhere больше не возвращают задачу, которая не собирается делать работу: cancelAll() и свежий запрос строкой позже отвечали на второй вызов хэндлом, который первый только что отменил.
  • cancelAll и удаления SoloQueue достают до задачи, которую движок вот-вот запустит: вызванные из правил старта этой задачи, они её пропускали, и она всё равно стартовала.
  • Policy.droppable, replace и restart достают до задачи, которую движок вот-вот запустит: вызванные из правил старта этой задачи с её ключом, они её пропускали, и две задачи одного ключа шли одна за другой.
  • publish, который бросает, больше не задерживает изменения, стоящие в очереди за ним, а правила работающих задач спрашиваются всё равно: задачу, которую новое состояние не устраивает, отменяют.
  • Предикат SoloQueue.removeWhere или Solo.lastJobWhere, который удаляет задачу, больше не натыкается на ConcurrentModificationError.
  • Завершённая задача отпускает своё тело, свои обработчики состояния и задачу, которая её запустила: один сохранённый хэндл держал всё дерево завершённых задач, из которого он вышел.
  • Задача, которую работа ctx.unattended одного контроллера запускает на другом контроллере, стартует в зоне, откуда работа была запущена: ошибка голого unawaited в её теле доходила до onError контроллера, чья задача запустила работу.
  • Cancelled, который бросает хук контроллера или наблюдателя, больше не доходит до зоны.

Первый опубликованный выпуск. 0.1.0 так и не покинула дерево, поэтому ни одна запись ниже не требует миграции; то, что изменилось после него, стоит в конце, для дерева, которое следило за пакетом до его выхода.

  • Добавлены обработчики состояния onError и onCancel у run и job. Они применяют состояние после детей и уборки, до следующей задачи в очереди, и сохраняют исход с провалом или отменой. Несовместимые внешние изменения состояния подавляют поправку, в том числе во время уборки и для потомков. Быстрый старт теперь использует sealed-состояния загрузки, результата и ошибки.

  • Добавлены AccumulationTiming.debounce и .throttle для collect и accumulate. Debounce запечатывает группу после паузы между событиями; throttle разносит фактические старты групп. Ждущие группы пропускают готовые задачи, а обработчики, дети и уборка остаются последовательными. collect хранит каждое событие; accumulate хранит результат merge. Закрытие отменяет таймеры timing и группы в очереди без финального пакета.

  • Ломающее: ctx.run(child) возвращает результат ребёнка как Future<T>. Замените await ctx.run(child).value на await ctx.run(child); сохраняйте исходный хэндл ребёнка, чтобы отменять его или смотреть его исход. После успеха run проверяет отмену родителя и правила состояния, прежде чем вернуться. Параллельные старты должны обработать возвращённую future или явно её проигнорировать. run контроллера и ctx.each по-прежнему сразу возвращают хэндлы задач.

  • Добавлены фабрики collect и accumulate, которые возвращают SoloAccumulator: они собирают события в список или сливают их в одно значение до выполнения. AccumulationPolicy выбирает группировку соседних, замену в хвосте очереди с переносом данных или присоединение к группе, которая уже стоит в очереди. Группы живут по обычному жизненному циклу SoloJob, с правилами состояния и отменой.

  • Из ядра реэкспортированы Job.then и ChainCancelReason. Продолжения поддерживают цепочки отмены и имеют собственный JobContext; они не наследуют состояние контроллера, правила, наблюдателей и место в очереди.

  • Движок SoloBase<S>: одна корневая задача за раз, исключительное владение состоянием.

  • Solo<S> с broadcast-стримом stream, доставка на следующей микротаске.

  • Задачи как async-тела с рабочим типом W, правилами canStart и keepWhile, cancellable: false.

  • Замена, отменённая синхронным слушателем, в очередь не попадает и потому не может поглотить более позднюю задачу droppable.

  • Требуется async_job: ^0.2.0: job.whenCancelled(callback) регистрирует синхронного слушателя, который получает Cancelled, и возвращает функцию, которая его снимает.

  • Хэндл Job<T>: done, value, outcome, whenCancelled, cancel, ignore.

  • Outcome<T>: Done, Failed, Cancelled с CancelReason.

  • Исход Failed, который никто не наблюдал, уходит в зону, создавшую задачу, так же, как Dart сообщает о необработанной ошибке Future. Чтение done или value и вызов ignore считаются наблюдением; Cancelled до зоны не доходит никогда.

  • JobContext ядра: check, wait, join, uncancellable, unattended, onCancel, run, each, log.

  • SoloContext<S, W> поверх него, где живёт состояние: state, stateAs, emit.

  • Тело не пишет await само по себе: каждый вызов идёт через контекст, и член, который оно выбирает, говорит, что отмена делает с этим вызовом. wait кончает ожидание и даёт действию идти дальше; join ждёт действие целиком и сдаётся после него, так что вызов устройства никогда не бросается на полпути; uncancellable удерживает отмену на время вызова и сдаётся, когда шаг кончился; unattended не ждёт вовсе и отдаёт работу движку, который слышит её провал даже после конца задачи.

  • ctx.unattended(action) для работы, которую тело запускает и не ждёт. Она идёт в собственной зоне ошибок, и всё, что она оставит неперехваченным, сейчас или много позже конца задачи, доходит до onError, а не до процесса.

  • Изменение поведения. Без SoloBase.observer и без переопределения SoloBase.onError ошибка, которой больше некуда идти, уже не останавливается в пустом хуке: она уходит в зону, где создана задача, так же, как ядро сообщает о ней, когда у задачи нет наблюдателя. Это касается уборки, колбэка onCancel, поздней ошибки брошенного вызова или работы, отданной unattended, и canStart или keepWhile, которые бросили вместо ответа, пока задача идёт. Cancelled составляет единственное исключение и туда не уходит никогда. Поставьте наблюдателя или переопределите хук, и путь снова ваш; чтобы сохранить его, вызовите super.onError(...) из переопределения. Правило, которое бросает до старта задачи, на этом пути вовсе не стоит: оно становится исходом Failed и доходит до зоны, как всякий ненаблюдённый провал, каким бы ни был наблюдатель.

  • Стек уборки вместо finally в теле: ctx.onDispose освобождает при любом исходе, ctx.onDiscard только когда задача кончается не Done, а wait и join принимают те же два как dispose и discard для значения, которое они передают: соединение или файл, открытые брошенным действием, всё равно закрываются. Движок разматывает стек после детей и до исхода, и close его ждёт; ctx.disown(value) забирает регистрацию обратно, когда тело передаёт значение само.

  • JobContext.uncancellable составляет пару к wait: он выполняет шаг, который нельзя взять назад (платёж на пути к серверу), удерживая отмену до конца шага, и close его ждёт.

  • Ломающее: ctx.each(stream, (child, event) { ... }) сразу возвращает дочернюю Job<void>. Ждите .value, если нужен результат, который бросает, смотрите .done или отменяйте подписку отдельно через .cancel(). Колбэк получает SoloContext<S, W>, который сохраняет рабочий тип и keepWhile родителя и не наследует canStart. Родитель ждёт этого ребёнка даже без явного await, и наблюдатели видят ребёнка. Отмена родителя идёт к нему каскадом.

  • Отмена each сразу снимает подписку, а затем ждёт идущий колбэк, прежде чем ребёнок завершится и уберёт за собой. Чтобы отвечать на отмену, пользуйтесь контрольными точками контекста ребёнка; обычный await может задержать отмену и close(). Уборку источника при отмене подписки по-прежнему не ждут. each теперь метод контекста и следует ограничениям run на то, когда и где могут стартовать дети.

  • JobContext.onCancel передаёт отмену тому, что умеет остановиться по-настоящему: токену отмены устройства, прерыванию HTTP-запроса. wait кончает ожидание, а не работу.

  • Дочерние задачи через ctx.run; родитель кончается после своих детей.

  • SoloQueue с remove, removeWhere, clear, lastWhere; политики sequential, droppable, replace, restart.

  • externalSetState для слушателей устройства; каждое изменение заново проверяет правила идущих задач.

  • SoloObserver и хуки экземпляра; трассировка движка SoloBase.debug.

  • Хук, который бросает, ничего не меняет: движок отдаёт его ошибку текущей зоне и идёт дальше, а хук рядом с ним всё равно вызывается.

  • Ядро задач теперь живёт в package:async_job и реэкспортируется целиком: package:solo/solo.dart остаётся единственным импортом. Код на старом API причин должен перейти на классы причин. Жизненный цикл задачи, контекст, исходы и наблюдатель переехали в ядро; состояние, очередь и правила остаются в solo.
  • CancelReason стал расширяемой иерархией классов: ManualCancelReason, ParentCancelReason и HandlerCancelReason из ядра плюс RulesCancelReason и ClosedCancelReason из solo. Проверяйте типы; name служит только меткой для журнала, равенства по имени нет. Свои подклассы могут нести любые данные через job.cancel(reason: reason). Передача от родителя и от ребёнка сохраняет исходную отмену в cause.
  • Cancelled.by({reason, started, description, stackTrace}) публичный: движок предметной области строит отмены с собственной причиной.
  • JobContext<S, W> переименован в SoloContext<S, W>, а имя JobContext теперь принадлежит интерфейсу ядра: отмена, семейство ожиданий и дети, без состояния. SoloContext реализует его и добавляет state, stateAs и emit. Тела задач этого имени не пишут, поэтому код, написанный по README, собирается как раньше.
  • Задача и её контекст разделены по шву, по которому ядро будет вынуто: JobBase<T> и JobContextBase несут жизненный цикл, семейство ожиданий, детей и исход, а подклассы solo добавляют состояние, правила и очередь. В публичном API контроллера ничего не сдвигается.
  • Список детей, которых ждёт задача, теперь сокращается по мере их завершения: долгоживущая задача, которая запускает детей в цикле, больше не растёт на запись за ребёнка. Описание собственного исхода родителя не изменилось: связь исхода с ребёнком, который его принёс, живёт в Expando, поэтому ребёнок без ключа в нём по-прежнему виден.
  • Значение, которое тело вернуло после того, как его отмена уже пришла, освобождается, а не отбрасывается: тело регистрирует его в стеке уборки, исходом остаётся отмена, и close ждёт освобождения.
  • Задача говорит, кто может её принять: ctx.run(child) отказывает задаче другого контроллера с ArgumentError, а задаче, которая ещё ждёт в очереди, со StateError, и задаче solo отказывает контекст голого ядра. Ребёнок без собственного наблюдателя наследует наблюдателя родителя.
  • JobBase.debug трассирует жизнь задачи, SoloBase.debug трассирует очередь, состояние и закрытие. Задайте оба, чтобы следить за обоими.
  • JobObserver служит наблюдателем одной задачи: onStart, onFinish, onError и onLog, без контроллера в сигнатурах. Контроллер кормит из него свой SoloObserver и свои хуки экземпляра, поэтому для пользователя solo ничего не меняется.
  • isQueued переезжает из Job<T> в новый SoloJob<T>, который возвращают job, add и run. Очередь принадлежит контроллеру, а не задаче. JobStatus публичный и имеет три значения: задача в очереди остаётся created, пока не стартует, а isQueued вычисляется по самой очереди: внутри canStart задача, взятая из очереди, в очереди уже не стоит.

Не публиковалась.