Освой Future, async/await и FutureBuilder, не блокируя интерфейс и не отправляя свои запросы заново по случайности.
Открыть этот урок в KodokonDart выполняет твоё приложение в одном потоке, которым управляет цикл событий. Future - это не поток: это обещание значения, которое придёт позже. await никогда не блокирует этот поток - он приостанавливает текущую функцию, возвращает управление циклу (интерфейс продолжает работать), а затем возобновляет её, когда приходит результат. Следствие: async/await ничего не распараллеливает для чистых вычислений; для тяжёлой работы с процессором нужен изолят.
Future<double> fetchRate() async {
await Future<void>.delayed(
const Duration(milliseconds: 300),
);
return 1.08;
}
Future<void> main() async {
try {
final rate = await fetchRate();
print('EUR/USD rate: $rate');
} on Exception catch (error) {
print('Rate loading failed: $error');
}
}Два идущих подряд await выполняются последовательно: второй стартует только после первого. Когда операции независимы, запускай их вместе через Future.wait. Компромисс, который надо знать: Future.wait работает по принципу fail-fast - первая же ошибка отклоняет всё целиком, и результаты остальных future теряются, даже если они успешны. Если тебе нужны частичные результаты, обрабатывай ошибку внутри каждого future.
Future<String> fetchProfile() async => 'profile';
Future<String> fetchOrders() async => 'orders';
Future<void> main() async {
final [profile, orders] = await Future.wait(
[fetchProfile(), fetchOrders()],
);
print('$profile / $orders');
}Со стороны интерфейса FutureBuilder связывает Future с build через snapshot. Ловушка номер один на практике: создать future прямо в build. Тогда каждая перерисовка - хватит простого setState у родителя - будет заново отправлять запрос. Сохраняй future в initState и всегда проверяй ошибку раньше данных.
import 'package:flutter/material.dart';
class ProfileScreen extends StatefulWidget {
const ProfileScreen({super.key});
@override
State<ProfileScreen> createState() =>
_ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen> {
late final Future<String> _nameFuture;
@override
void initState() {
super.initState();
_nameFuture = _loadName();
}
Future<String> _loadName() async {
await Future<void>.delayed(
const Duration(seconds: 1),
);
return 'Ada Lovelace';
}
@override
Widget build(BuildContext context) {
return FutureBuilder<String>(
future: _nameFuture,
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
}
return Text(snapshot.data!);
},
);
}
}await в главном потоке?Future не разрешится.Future.wait([a(), b()]), если a() завершится ошибкой?Future.wait отклоняется с ошибкой из a(); результат b() теряется.Future.wait дожидается конца b(), а затем возвращает только его результат.b() успешен.future: fetchUser() прямо в build вместе с FutureBuilder?build не может содержать асинхронный вызов, код не компилируется.Future и, значит, заново отправляла бы сетевой запрос.snapshot навсегда застрял бы в состоянии ожидания.