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

Асинхронность, Future и Stream

Dart выполняет весь ваш код в одном потоке, чередуя куски работы. Future — это одно значение, которое придёт позже; Stream — много таких значений. Понимание того, как под всем этим устроен event loop, отличает «поставить await и надеяться» от знания, что и когда выполняется.

Почему это важно. Почти любой плавающий баг во Flutter-приложении — это баг порядка: что-то завершилось после того, как экран уже уничтожен; два запроса вошли в гонку; подписка пережила свой widget. Ни один из них не воспроизводится стабильно, и все они растут из асинхронности, которую никто не продумал.

Что нужно понимать

  • Что выполняется, пока вы стоите на await, — и что не выполняется
  • Нужен ли ещё результат операции к моменту, когда она завершится
  • Кто владеет подпиской и кто её закрывает
  • Допустимо ли, чтобы две операции, способные наложиться друг на друга, действительно наложились
  • Куда уходит ошибка, если её никто не ждёт

Ключевые темы

Event loop

  • Микрозадачи и события — и почему порядок их выполнения удивляет
  • Что на самом деле делает await: приостановиться здесь, продолжить позже, в том же потоке
  • Блокировка цикла — и почему тяжёлая синхронная работа подвешивает UI

Future

  • Создание, цепочки и разница между «вернуть» и «дождаться»
  • Future.wait для параллельной работы и её принцип «всё или ничего» при ошибке
  • Ошибки: try/catch вокруг await, catchError и необработанные ошибки
  • Таймауты и отмена, которых Dart не даёт из коробки

Stream

  • Single-subscription против broadcast — и цена неверного выбора
  • Преобразования: map, where, asyncMap, distinct, debounce через сторонние пакеты
  • Backpressure — быстрый производитель и медленный потребитель
  • StreamController и его закрытие
  • Подписка как ресурс со своим временем жизни

Типичные сбои

  • Работа завершается, когда widget уже уничтожен
  • Два одновременных запроса приходят не в том порядке
  • Утёкшие подписки
  • async там, где результат никто не ждёт, — и ошибки исчезают бесследно

Уровни

УровеньКак это выглядит
JuniorКорректно применяет async/await для простых вызовов и обрабатывает ошибки вокруг них.
MiddleОсознанно выбирает между Future и Stream, отменяет подписки, справляется с гонками и таймаутами.
SeniorРассуждает о порядке выполнения в масштабе целой фичи. Проектирует конвейеры потоков с backpressure и корректной очисткой, а плавающий баг умеет объяснить с самых основ.

Практика

Для начала

  • Обработайте ошибку по-настоящему Возьмите await без обработки ошибок и задайте разное поведение для сбоя и для таймаута.

  • Запустите два вызова параллельно Замените два последовательных await на Future.wait, а затем решите, что должно произойти, если один из вызовов упадёт.

  • Закройте подписку Найдите listen, которому не соответствует ни один cancel, и приведите жизненный цикл в порядок.

Дальше

  • Выиграйте гонку Сделайте поле поиска по мере ввода так, чтобы медленный ранний ответ не мог перезаписать быстрый поздний.

  • Соберите конвейер Составьте поток с debounce, устранением дублей и восстановлением после ошибок — и покройте его тестами.

  • Найдите микрозадачу Напишите код, порядок вывода которого вас удивит, а затем объясните его через event loop.

Проверьте себя

  • Что произойдёт в вашем приложении, если ответ придёт после того, как пользователь ушёл с экрана?
  • Какие из ваших потоков — broadcast, и был ли это осознанный выбор?
  • Где два запроса могут войти в гонку и кто выигрывает её сегодня?
  • Что случится с исключением, брошенным внутри async-функции, которую никто не ждёт?
  • Как вы отменяете работу, результат которой больше не нужен?
  • Что в вашем приложении способно заблокировать event loop настолько, чтобы пропустить кадр?

Материалы

  • Asynchronous programming: futures, async, await — официальный codelab и правильное место, чтобы починить шаткое понимание await, прежде чем браться за что-то ещё.
  • Concurrency in Dart — event loop, изоляты и то, что на самом деле значит «однопоточный». Коротко и принципиально важно.
  • Using streams — потребление и преобразование потоков, включая обработку ошибок, которую большинство руководств пропускает.
  • Stream class reference — стоит один раз прочитать целиком. Большая часть того, что люди пишут руками, здесь уже есть готовым методом.
  • Dart asynchronous programming: isolates and event loops — от команды Dart, самый внятный разбор того, как цикл работает на самом деле.