Beherrsche Future, async/await und FutureBuilder, ohne die UI zu blockieren oder deine Anfragen versehentlich erneut auszulösen.
Diese Lektion in Kodokon öffnenDart führt deine Anwendung auf einem einzigen Thread aus, angetrieben von einer Ereignisschleife. Ein Future ist kein Thread: Es ist das Versprechen auf einen künftigen Wert. await blockiert diesen Thread nie - es pausiert die aktuelle Funktion, gibt die Kontrolle an die Schleife zurück (die UI läuft weiter) und setzt fort, sobald das Ergebnis eintrifft. Daraus folgt: async/await parallelisiert bei reiner Berechnung gar nichts; für rechenintensive Arbeit brauchst du ein 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');
}
}Zwei aufeinanderfolgende await laufen nacheinander: Das zweite startet erst, wenn das erste fertig ist. Wenn die Operationen unabhängig sind, starte sie zusammen mit Future.wait. Der Kompromiss, den du kennen solltest: Future.wait ist fail-fast - der erste Fehler lässt das Ganze scheitern und die Ergebnisse der übrigen Futures gehen verloren, selbst wenn sie erfolgreich sind. Wenn du Teilergebnisse brauchst, behandle den Fehler innerhalb jedes einzelnen Futures.
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');
}Auf der UI-Seite verbindet FutureBuilder ein Future über ein snapshot mit build. Die Falle Nummer eins in der Praxis: das Future direkt in build zu erzeugen. Jeder Rebuild - ein einfaches setState eines Elternteils genügt - würde dann die Anfrage erneut auslösen. Speichere das Future in initState und prüfe immer den Fehler vor den Daten.
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 auf dem Haupt-Thread tatsächlich?Future aufgelöst ist.Future.wait([a(), b()]), wenn a() fehlschlägt?Future.wait scheitert mit dem Fehler von a(); das Ergebnis von b() geht verloren.Future.wait wartet, bis b() fertig ist, und liefert dann dessen Ergebnis allein zurück.b() erfolgreich ist.future: fetchUser() nicht direkt in build mit FutureBuilder schreiben?build darf keinen asynchronen Aufruf enthalten, der Code kompiliert nicht.Future erzeugen und damit die Netzwerkanfrage erneut auslösen.snapshot bliebe in einem Wartezustand stecken.