Асинхронность: Future, async и await

Future — это обещание значения в будущем; async/await позволяют ждать его, не замораживая интерфейс.

Суть: Future представляет результат, который появится позже (ответ сервера, файл). async помечает функцию асинхронной, await ждёт завершения Future, а интерфейс при этом остаётся отзывчивым.

Рядом с Future стоит его «многоразовый» собрат — Stream. Если Future — это одно значение в будущем, то Stream — это поток значений во времени: тики таймера, сообщения чата, обновления геолокации. Их слушают через await for или подписку, а во Flutter отображают через StreamBuilder, родственник FutureBuilder. Освоив Future, вы почти бесплатно понимаете и Stream, ведь идея отложенного результата у них общая.

Сетевой запрос занимает время. Если выполнять его синхронно, интерфейс замёрзнет — анимации встанут, нажатия не сработают. Dart решает это асинхронностью. Функция, делающая долгую работу, возвращает Future — «обещание» вернуть результат позже. С async/await код выглядит как обычный последовательный, но не блокирует поток.

Future<String> fetchUser() async {
  await Future.delayed(const Duration(seconds: 2));  // имитация запроса
  return 'Аня';
}

Future<void> loadData() async {
  print('Загрузка...');
  try {
    final name = await fetchUser();   // ждём, не блокируя UI
    print('Получено: $name');
  } catch (e) {
    print('Ошибка: $e');              // ловим сбои
  }
}

async превращает возвращаемое значение в Future. await приостанавливает функцию до готовности результата, отдавая управление обратно интерфейсу. Ошибки ловят привычным try/catch.

Как работает event loop под капотом

Dart однопоточен, но не блокируется благодаря циклу событий (event loop). Когда вы делаете await, функция «ставится на паузу», а поток свободен обрабатывать другие события — отрисовку, нажатия. Как только Future завершается, продолжение функции ставится в очередь и выполняется. Так один поток успевает и ждать сеть, и рисовать плавный интерфейс.

  loadData() вызвана
        |
        v
  print('Загрузка...')
        |
        v
  await fetchUser()  -----+   функция на паузе
        |                 |   поток свободен:
        |                 +-> рисует UI, ловит тапы
        |                 |
   (через 2 сек)          |
   Future завершён  <------+
        |
        v
  продолжение: print('Получено: Аня')
# Модель async/await на Python через asyncio (тот же event loop)
import asyncio

async def fetch_user():
    await asyncio.sleep(1)        # имитация сетевой задержки
    return 'Аня'

async def load_data():
    print('Загрузка...')
    try:
        name = await fetch_user()  # ждём, поток свободен
        print('Получено:', name)
    except Exception as e:
        print('Ошибка:', e)

asyncio.run(load_data())

Частые ошибки

  • Забыть await — получите сам Future вместо значения, и код пойдёт дальше до готовности результата.
  • Не обработать ошибку — сетевой сбой уронит приложение; оборачивайте в try/catch.
  • setState после await на удалённом виджете — проверяйте if (mounted).

Best practices

  • Помечайте долгие функции async и всегда await-ьте их результат.
  • Оборачивайте сетевые вызовы в try/catch и показывайте пользователю состояние ошибки.
  • Не делайте тяжёлых вычислений в основном потоке даже асинхронно — для них есть Isolate/compute.

Важно не путать асинхронность с многопоточностью. await не создаёт новый поток — он лишь уступает управление, пока ждёт результата. Поэтому асинхронность отлично подходит для ожидания (сеть, диск), но не ускоряет тяжёлые вычисления, которые всё равно займут единственный поток. Для по-настоящему долгих расчётов в Dart есть Isolate и удобная обёртка compute, выносящие работу в отдельный поток. Различать эти случаи — признак зрелого понимания.

Итог: Future и async/await — основа отзывчивого приложения. Понимание event loop объясняет, почему один поток Dart не виснет на запросах. Осталось связать асинхронные данные с интерфейсом — это сделает FutureBuilder в следующем уроке.

Проверьте себя
1. Что представляет собой Future в Dart?
AГотовое значение прямо сейчас
BОбещание значения, которое появится позже (например, ответ сервера)
CТип виджета
DСиноним setState
2. Почему await не «замораживает» интерфейс приложения?
ADart использует много потоков
BНа время ожидания поток свободен и обрабатывает другие события через event loop
Cawait отключает интерфейс
DFuture выполняется мгновенно