Kodokon kodokon.com

Asynchronität in Dart: Future, async/await, FutureBuilder

Beherrsche Future, async/await und FutureBuilder, ohne die UI zu blockieren oder deine Anfragen versehentlich erneut auszulösen.

10 Min. · 3 Fragen

Diese Lektion in Kodokon öffnen

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

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');
  }
}
Eine asynchrone Funktion wird mit await und try/catch konsumiert.

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.

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');
}
Parallelisierung mit Future.wait und Destrukturierung aus Dart 3.

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.

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!);
      },
    );
  }
}
Das Future wird nur ein einziges Mal erzeugt, in initState.

Wissenscheck

Stelle sicher, dass du die wichtigsten Punkte dieser Lektion behalten hast.

  1. Was macht await auf dem Haupt-Thread tatsächlich?
    • Es blockiert den Thread, bis das Future aufgelöst ist.
    • Es verschiebt die Berechnung automatisch auf einen zweiten Thread.
    • Es pausiert die aktuelle Funktion und gibt die Kontrolle an die Ereignisschleife zurück: Die UI läuft weiter.
  2. Was passiert bei 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.
    • Der Fehler wird ignoriert, solange b() erfolgreich ist.
  3. Warum sollte man future: fetchUser() nicht direkt in build mit FutureBuilder schreiben?
    • build darf keinen asynchronen Aufruf enthalten, der Code kompiliert nicht.
    • Jeder Rebuild würde ein neues Future erzeugen und damit die Netzwerkanfrage erneut auslösen.
    • Das snapshot bliebe in einem Wartezustand stecken.