Асинхронность: 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 в следующем уроке.