Qu’est-ce qu’une donnée asynchrone ?
Une donnée asynchrone est une donnée qui n’est pas disponible immédiatement lorsque votre widget se construit. À l’inverse d’une donnée synchrone, obtenue directement (comme une simple addition), une donnée asynchrone demande un délai pour être récupérée ou traitée. Cela arrive souvent avec des tâches comme interroger une API distante, lire un fichier sur le disque, ou réaliser un calcul complexe.
Mais on peut alors se demander : Pourquoi ne pas simplement attendre que ces tâches se terminent avant de continuer ? La réponse tient dans la façon dont Flutter (et Dart) fonctionne.
Comprendre ce qu’est l’Event loop
Pour mieux comprendre pourquoi les fonctions asynchrones sont nécessaires, il faut d’abord comprendre comment se construit une application Flutter.
Avec Flutter, toute la construction de l’application repose sur un seul fil d’exécution principal, appelé thread UI. Celui-ci doit gérer à la fois le rendu de l’interface et l’exécution du code, en suivant les tâches une par une. On appelle ce mécanisme de construction de l’application, l’Event loop.
Imaginez un chef d’orchestre qui dirige une symphonie et qui ne peut donner qu’une seule instruction à la fois, mais doit gérer plusieurs musiciens qui jouent à des moments différents.
Voici comment ça fonctionne :
- La pile d’appels (Call Stack) : C’est là où les tâches immédiates (synchrones) sont exécutées, comme afficher un texte ou faire un calcul rapide. Elles sont traitées une par une, dans l’ordre.
- La file d’attente (Event Queue) : Quand une tâche prend du temps (comme charger des données depuis une API), elle est mise dans cette file d’attente. L’Event Loop la récupère et l’exécute seulement quand la pile d’appels est vide.
Voyons ce que ça donne avec un exemple :
void main() {
print("Début");
Future.delayed(Duration(seconds: 2), () => print("Tâche lente"));
print("Fin");
}
Ici, Flutter va d’abord gérer les tâches synchrones, qui peuvent être exécutées immédiatement, puis revenir plus tard pour ma tâche asynchrone. Voilà ce qui va s’afficher dans la console :
Début
Fin
(2 secondes plus tard)
Tâche lente
Ici, l’Event Loop exécute d’abord les tâches synchrones (print(« Début ») et print(« Fin »)), puis, une fois la pile vide, il va chercher la tâche asynchrone dans la file d’attente après le délai de 2 secondes. Sans ce mécanisme, l’appli serait bloquée pendant ces 2 secondes, ce qui ruinerait l’expérience utilisateur.
Pourquoi utiliser des fonctions asynchrones ?
Les fonctions asynchrones (avec Future
, async
, et await
) sont la manière dont on « dialogue » avec l’Event Loop. Puisque Dart ne peut pas exécuter plusieurs tâches en parallèle sur son thread principal, on doit déléguer les opérations longues à ce système pour éviter de bloquer l’interface. Voici pourquoi elles sont indispensables :
Les fonctions asynchrones permettent de :
- Préserver la fluidité de l’application : Si vous chargez des données depuis une API sans fonction asynchrone, le thread UI attendrait la réponse, gelant l’écran. Avec async, l’Event Loop prend le relais, et l’appli reste interactive.
- Gérer des tâches dépendantes du temps : Les appels réseau (HTTP), l’accès aux fichiers ou aux bases de données, et même le téléchargement d’images sont imprévisibles en termes de durée. Les fonctions asynchrones permettent de les lancer sans stopper toute l’application.
- Optimiser les opérations coûteuses : Un calcul lourd ou un traitement d’image peut prendre plusieurs secondes. En le rendant asynchrone, vous laissez l’Event Loop s’en occuper sans perturber le rendu.
Voici un exemple avec une fonction asynchrone qui dit à l’Event Loop : « Occupe-toi d’autre chose pendant ces 3 secondes, et reviens quand c’est prêt. » :
Future<String> fetchData() async {
await Future.delayed(Duration(seconds: 3)); // Simule une requête API
return "Données récupérées !";
}
void main() async {
print("Chargement...");
String data = await fetchData();
print(data);
}
Désavantages d’une fonction asynchrone
Présenté comme ça, on pourrait penser qu’il serait intéressant de rendre toutes nos fonctions asynchrones pour charger notre page plus rapidement. En réalité les choses sont plus complexes que cela. Il n’existe pas solution miracle et les fonctions asynchrones présentent certains désavantages qui doivent vous faire limiter leur usage au strict nécessaire :
- Complexité accrue du code : L’utilisation de fonctions asynchrones peut rendre le code plus complexe à lire et à déboguer, car il est plus difficile de suivre le flux des évènements.
- Gestion de plusieurs fonctions à la suite : Bien que les fonctions asynchrones permettent de ne pas bloquer le thread principal, elles ne fonctionnent pas toutes en parallèle par défaut. Vous devrez donc gérer savoir l’ordre d’exécution, pour éviter des blocages potentiels ou des comportements inattendus. Parfois, il peut donc être plus simple d’exécuter certaines opérations de manière synchrone si elles ne prennent qu’une fraction de seconde.
- Imprévisibilité des résultats : Avec les fonctions asynchrones, il est parfois difficile de prévoir quand exactement une tâche sera terminée. Par exemple, si vous dépendez d’un appel réseau, vous devez anticiper les possibles délais, pannes de réseau, ou erreurs de connexion.
Pour des opérations rapides qui ne nécessitent pas d’aller chercher des ressources à distance, la mise en place de fonctions asynchrones n’est donc pas nécessaire, voire contre-productif. On risque simplement de désorganiser l’Event loop pour rien.
Comment mettre en place des fonctions asynchrones dans Flutter avec async
et await
?
Dans Flutter (et Dart en général), la gestion des fonctions asynchrones se fait principalement à l’aide des mots-clés async
et await
.
async
: Ce mot-clé est utilisé pour marquer une fonction comme étant asynchrone. Cela signifie que la fonction peut contenir des opérations qui prendront un certain temps à s’exécuter.await
: Ce mot-clé est utilisé à l’intérieur d’une fonction marquéeasync
. Il indique à Dart d’attendre l’achèvement de l’opération asynchrone avant de continuer l’exécution de la fonction.
Voici un exemple simple d’une application qui affiche du texte de l’écran après un temps de chargement (simulé) de 2 secondes :
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Async Await Demo',
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
String _data = "Chargement...";
// Fonction asynchrone qui simule une tâche longue
Future<void> _fetchData() async {
await Future.delayed(Duration(seconds: 2)); // Simule une attente de 2 secondes
setState(() {
_data = "Données récupérées après 2 secondes";
});
}
@override
void initState() {
super.initState();
_fetchData(); // Appel de la fonction asynchrone au démarrage de l'application
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Async Await Demo'),
),
body: Center(
child: Text(
_data,
style: TextStyle(fontSize: 20),
),
),
);
}
}
Explication du code :
Ici, la fonction asynchrone _fetchData simule une attente de 2 secondes avec Future.delayed
. Une fois l’attente terminée, elle met à jour l’interface utilisateur en appelant setState
.
Au lancement, l’application affiche le texte « Chargement… ». Puis, après 2 secondes, le texte est mis à jour avec « Données récupérées après 2 secondes » grâce à la fonction asynchrone qui s’exécute en arrière-plan.
Conclusion
Maintenant que vous comprenez le rôle des fonctions asynchrones, vous êtes prêts à récupérer des ressources en ligne et à les afficher dans votre application de manière fluide et réactive. Ce mécanisme est essentiel pour rendre une application dynamique, et il est au cœur de nombreuses applications modernes telles que les services de messagerie, les plateformes d’achat en ligne, ou les applications de gestion de listes.
Mais quel widget vous permet de récupérer et d’afficher ces informations en temps réel ? C’est ce que nous allons explorer dans les deux prochains articles.