Maîtrisez Future, async/await et FutureBuilder sans bloquer l'UI ni relancer vos requêtes par accident.
Ouvrir cette leçon dans KodokonDart exécute votre application sur un seul thread, animé par une boucle d'événements. Un Future n'est pas un thread : c'est la promesse d'une valeur à venir. await ne bloque jamais ce thread - il suspend la fonction courante, rend la main à la boucle (l'UI continue de tourner), puis reprend quand le résultat arrive. Corollaire : async/await ne parallélise rien pour du calcul pur ; pour du CPU intensif, il faut un isolate.
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');
}
}Deux await successifs s'exécutent en série : le second ne démarre qu'après le premier. Quand les opérations sont indépendantes, lancez-les ensemble avec Future.wait. Le compromis à connaître : Future.wait est fail-fast - la première erreur rejette l'ensemble et les résultats des autres futures sont perdus, même s'ils aboutissent. Si vous avez besoin de résultats partiels, gérez l'erreur à l'intérieur de chaque 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');
}Côté interface, FutureBuilder connecte un Future à build via un snapshot. Le piège numéro un du métier : créer le future directement dans build. Chaque rebuild - un simple setState d'un parent suffit - relancerait alors la requête. Mémorisez le future dans initState et testez toujours l'erreur avant les données.
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 sur le thread principal ?Future.Future.wait([a(), b()]), que se passe-t-il si a() échoue ?Future.wait rejette avec l'erreur de a() ; le résultat de b() est perdu.Future.wait attend la fin de b() puis renvoie son résultat seul.b() réussit.future: fetchUser() écrit directement dans build avec FutureBuilder ?build ne peut pas contenir d'appel asynchrone, le code ne compile pas.Future, donc relancerait la requête réseau.snapshot resterait bloqué en état d'attente.