dart-asincronia-future-async-await

Asincronía en Dart: Future, async y await

  • 5 min

La asincronía es la capacidad de esperar operaciones lentas sin bloquear el resto del programa.

Hasta ahora, todo nuestro código se ejecutaba de forma inmediata: hacíamos una suma y el resultado estaba ahí al instante. Pero en el mundo real, y sobre todo en el desarrollo de Apps móviles, las cosas tardan tiempo.

Leer un archivo, consultar una base de datos o pedir información a internet no es instantáneo. Si nuestro código se “queda quieto” esperando a que internet responda, la aplicación se congelará y el usuario pensará que se ha roto.

Para evitar esto, Dart utiliza la Asincronía. Hoy vamos a dominar el tiempo en nuestro código.

Dart, los isolates y el event loop

Para entender la asincronía, primero hay que entender cómo funciona Dart por dentro.

El código Dart se ejecuta dentro de isolates. Cada isolate tiene su propia memoria y procesa el código Dart en un único hilo mediante un event loop. Una aplicación puede crear más isolates para trabajo en paralelo, pero la asincronía habitual no necesita hacerlo.

¿Entonces, cómo es posible descargar una imagen mientras el usuario hace scroll? Gracias al event loop, que permite continuar procesando eventos mientras una operación externa termina.

Imagina que eres un camarero (el hilo de Dart):

Un cliente te pide un café (Operación asíncrona).

Tú anotas el pedido, se lo pasas a la cocina y sigues atendiendo a otros clientes (sigues pintando la pantalla).

Cuando la cocina termina el café, te avisa (Evento).

Tú sirves el café cuando tienes un hueco libre.

Si te quedaras parado en la barra esperando a que el café se haga, el restaurante se bloquearía. Eso es la asincronía: iniciar una tarea y prometer ocuparse del resultado cuando esté listo.

El objeto Future

En Dart, esa “promesa” de que algo ocurrirá en el futuro se representa con el objeto Future.

Un Future es como una caja cerrada.

  • Al principio está pendiente (la caja está cerrada).
  • Después de un tiempo, se completa (la caja se abre).
  • Puede completarse con un valor (éxito).
  • O completarse con un error (fallo).

La sintaxis del tipo de dato es Future<TipoDeDato>. Por ejemplo, Future<String> significa “Te prometo que aquí habrá un texto, pero más tarde”.

Simulando una espera

Vamos a usar Future.delayed para simular que estamos descargando datos de internet.

void main() {
  print('Inicio del programa');
  
  // Simulamos una petición que tarda 2 segundos
  httpGet('https://api.luisllamas.es/data');
  
  print('Fin del programa');
}

Future<String> httpGet(String url) {
  return Future.delayed(Duration(seconds: 2), () {
    return 'Respuesta de la API';
  });
}
Copied!

Si ejecutas esto, verás algo curioso en la consola:

  1. Inicio del programa
  2. Fin del programa
  3. (2 segundos después)… (Aquí no imprimimos nada aún, pero la tarea termina).

¡Dart no esperó! Lanzó la petición y siguió ejecutando el print('Fin...').

async y await

Antiguamente, para manejar el resultado del Future usábamos .then(), lo que llevaba a un código anidado difícil de leer (“Callback Hell”).

Dart moderno introdujo las palabras clave async y await, que nos permiten escribir código asíncrono como si fuera síncrono (lineal).

async

Transforma una función normal en una función asíncrona. Obliga a que la función devuelva un Future.

await

Le dice a Dart: “Espera aquí (sin bloquear la App) hasta que este Future se complete y dame su valor”.

Regla básica: Solo puedes usar await dentro de una función marcada como async.

Vamos a arreglar el ejemplo anterior:

// 1. Marcamos el main como async
Future<void> main() async {
  print('Inicio del programa');
  
  // 2. Usamos await para esperar el resultado
  String data = await httpGet('https://api.luisllamas.es/data');
  
  // Esta línea NO se ejecuta hasta que httpGet termine
  print(data);
  
  print('Fin del programa');
}

Future<String> httpGet(String url) {
  return Future.delayed(Duration(seconds: 2), () {
    return 'Hola Mundo - Datos recibidos';
  });
}
Copied!

Salida con await:

  1. Inicio del programa
  2. (espera 2 segundos…)
  3. Hola Mundo - Datos recibidos
  4. Fin del programa

¡Ahora sí! Hemos logrado controlar el flujo del tiempo.

Manejo de errores con try-catch

¿Qué pasa si la petición falla (no hay internet)? El Future se completará con error y, si no lo controlamos, nuestra App explotará (Unhandled Exception).

Con async/await, el manejo de errores es tan sencillo como usar un bloque try-catch de toda la vida.

Future<void> main() async {
  print('Conectando...');
  
  try {
    String data = await simularError();
    print(data);
  } catch (error) {
    print('¡Ups! Ha ocurrido un error: $error');
  }
}

Future<String> simularError() {
  return Future.delayed(Duration(seconds: 1), () {
    throw 'Servidor no encontrado'; // Lanzamos una excepción
  });
}
Copied!

Un error común: olvidar await

Un error típico de principiante en Flutter es llamar a una función asíncrona sin usar await cuando realmente necesitas el dato.

Future<void> cargarUsuario() async {
  var usuario = obtenerUsuarioBaseDatos(); // ⚠️ Falta el await
  
  // Aquí 'usuario' no es el dato, ¡es el objeto Future entero!
  // print(usuario.nombre); // Error: El objeto Future no tiene propiedad nombre
}
Copied!

Si olvidas el await, obtienes la “caja” (el Future), no el contenido de la caja.