Kodokon kodokon.com

Asincronía en Dart: Future, async/await, FutureBuilder

Domina Future, async/await y FutureBuilder sin bloquear la interfaz ni relanzar tus peticiones por accidente.

10 min · 3 preguntas

Abrir esta lección en Kodokon

Dart ejecuta tu aplicación en un único hilo, impulsado por un bucle de eventos. Un Future no es un hilo: es la promesa de un valor por venir. await nunca bloquea ese hilo: suspende la función actual, devuelve el control al bucle (la interfaz sigue funcionando) y luego se reanuda cuando llega el resultado. Corolario: async/await no paraleliza nada para el cálculo puro; para el trabajo intensivo de CPU, necesitas 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');
  }
}
Una función asíncrona se consume con await y try/catch.

Dos await sucesivos se ejecutan en serie: el segundo solo empieza después del primero. Cuando las operaciones son independientes, lánzalas juntas con Future.wait. El compromiso que hay que conocer: Future.wait es fail-fast (falla rápido): el primer error rechaza todo el conjunto y los resultados de los otros futures se pierden, aunque tengan éxito. Si necesitas resultados parciales, gestiona el error dentro de cada 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');
}
Paralelización con Future.wait y la desestructuración de Dart 3.

Del lado de la interfaz, FutureBuilder conecta un Future con build a través de un snapshot. La trampa número uno en la práctica: crear el future directamente en build. Cada reconstrucción (basta con un simple setState de un padre) relanzaría entonces la petición. Guarda el future en initState y comprueba siempre el error antes que los datos.

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!);
      },
    );
  }
}
El future se crea una sola vez, en initState.

Prueba de conocimientos

Comprueba que has retenido los puntos clave de esta lección.

  1. ¿Qué hace realmente await en el hilo principal?
    • Bloquea el hilo hasta que el Future se resuelve.
    • Traslada automáticamente el cálculo a un hilo secundario.
    • Suspende la función actual y devuelve el control al bucle de eventos: la interfaz sigue funcionando.
  2. Con Future.wait([a(), b()]), ¿qué ocurre si a() falla?
    • Future.wait se rechaza con el error de a(); el resultado de b() se pierde.
    • Future.wait espera a que b() termine y luego devuelve solo su resultado.
    • El error se ignora mientras b() tenga éxito.
  3. ¿Por qué evitar future: fetchUser() escrito directamente en build con FutureBuilder?
    • build no puede contener una llamada asíncrona, el código no compila.
    • Cada reconstrucción crearía un nuevo Future y, por tanto, relanzaría la petición de red.
    • El snapshot se quedaría atascado en un estado de espera.