Domina Future, async/await y FutureBuilder sin bloquear la interfaz ni relanzar tus peticiones por accidente.
Abrir esta lección en KodokonDart 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.
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');
}
}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.
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');
}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.
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 en el hilo principal?Future se resuelve.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.b() tenga éxito.future: fetchUser() escrito directamente en build con FutureBuilder?build no puede contener una llamada asíncrona, el código no compila.Future y, por tanto, relanzaría la petición de red.snapshot se quedaría atascado en un estado de espera.