Kodokon kodokon.com

L'asynchrone en Dart : Future, async/await, FutureBuilder

Maîtrisez Future, async/await et FutureBuilder sans bloquer l'UI ni relancer vos requêtes par accident.

10 min · 3 questions

Ouvrir cette leçon dans Kodokon

Dart 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.

DART
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');
  }
}
Une fonction async se consomme avec await et try/catch.

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.

DART
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');
}
Parallélisation avec Future.wait et destructuration Dart 3.

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.

DART
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!);
      },
    );
  }
}
Le future est créé une seule fois, dans initState.

Quiz de validation

Vérifiez que vous avez bien retenu les points clés de cette leçon.

  1. Que fait réellement await sur le thread principal ?
    • Il bloque le thread jusqu'à la résolution du Future.
    • Il déplace automatiquement le calcul sur un thread secondaire.
    • Il suspend la fonction courante et rend la main à la boucle d'événements : l'UI continue de tourner.
  2. Avec 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.
    • L'erreur est ignorée tant que b() réussit.
  3. Pourquoi éviter future: fetchUser() écrit directement dans build avec FutureBuilder ?
    • build ne peut pas contenir d'appel asynchrone, le code ne compile pas.
    • Chaque rebuild créerait un nouveau Future, donc relancerait la requête réseau.
    • Le snapshot resterait bloqué en état d'attente.