Dart programación asíncrona

La programación asíncrona es una de las capacidades más esenciales en el desarrollo de aplicaciones modernas.

Ya sea para solicitudes de red, lectura/escritura de archivos u operaciones de base de datos, se necesita procesamiento asíncrono para evitar bloquear el hilo principal.

En este capítulo se presentan el concepto de Future, la sintaxis async/await y cómo manejar errores asíncronos.


Por qué se necesita la programación asíncrona

Primero comprendamos la diferencia entre síncrono y asíncrono.

El código síncrono se ejecuta línea por línea en orden; la siguiente línea no comienza hasta que la anterior haya terminado.

Si una operación requiere espera (como una solicitud de red), todo el programa se bloquea.

Ejemplo

Comparación del efecto de ejecución entre código síncrono y asíncrono:

// Simular una operación que consume tiempo
String fetchDataSync() {
  // Código síncrono: supongamos que aquí se esperan 2 segundos
  // Durante estos 2 segundos, todo el programa no puede hacer nada
  sleep(Duration(seconds: 2));  // Espera bloqueante
  return 'Obtención de datos completada';
}

// Versión asíncrona: no bloquea el hilo principal
Future<String> fetchDataAsync() async {
  // Espera asíncrona: durante estos 2 segundos el programa puede continuar ejecutando otras tareas
  await Future.delayed(Duration(seconds: 2));
  return 'Obtención de datos completada';
}

void main() async {
  print('Comenzando a obtener datos de forma síncrona...');
  var syncResult = fetchDataSync();
  print('Resultado síncrono: $syncResult');
  print('(Nota: el programa se bloqueó durante la sincronía)');

  print('\n'Comenzando a obtener datos de forma asíncrona...');
  print('Después de enviar la solicitud, el programa continúa ejecutando otras tareas...');
  var asyncResult = await fetchDataAsync();
  print('Resultado asíncrono: $asyncResult');
  print('(Durante el proceso asíncrono, el programa no se bloqueó)');
}
Comenzando a obtener datos de forma síncrona...
Resultado de sincronización: obtención de datos completada
(Nota: el programa se quedó bloqueado durante la sincronización)

Comenzando a obtener datos de forma asíncrona...
Después de enviar la solicitud, el programa continúa ejecutando otras tareas...
Resultado asíncrono: obtención de datos completada
(El programa no se bloqueó durante la asincronía)

En Dart, todas las operaciones de E/S (archivos, red, base de datos) deben realizarse de forma asíncrona. La E/S síncrona bloquea todo el Isolate y en aplicaciones Flutter puede causar bloqueos de la interfaz de usuario (UI).


Concepto de Future

FutureRepresenta un valor que "se completará en algún momento futuro".

Es como una promesa: ahora no tengo el resultado, pero en el futuro te daré un valor de tipo T (o un error).

Future tiene tres estados:

EstadoDescripción
Incompleto (Uncompleted)La operación asíncrona todavía está en curso
Completado con valor (Completed with data)La operación tuvo éxito; Future lleva el valor del resultado
Completado con error (Completed with error)La operación falló; Future lleva la información del error

Ejemplo

Crear y usar Future:

void main() {
  print('Inicio del programa');

  // Crear un Future
  Future<String> future = Future(() {
    // Esta función se ejecutará en algún momento futuro
    return 'Resultado asíncrono de EJEMPLO';
  });

  print('Future se ha creado, pero aún no se ha completado');

  // Registrar el callback: se ejecuta cuando Future se completa
  future.then((result) {
    print('Resultado recibido: $result');
  });

  print('El programa continúa ejecutándose...');
}
程序开始
Future 已创建,但还没完成
程序继续执行...
收到结果: EJEMPLO 异步结果

Observa el orden de la salida: primero se imprime "El programa continúa ejecutándose" y luego "Resultado recibido".

Esto indica que el callback de Future se ejecuta de forma asíncrona y no bloquea el código posterior.


Sintaxis async / await

async y await son la sintaxis central de Dart para el manejo de operaciones asíncronas.

async marca una función como asíncrona; await espera a que un Future se complete y obtiene su resultado.

Ejemplo

// La palabra clave async marca la función como asíncrona
// El tipo de retorno de una función asíncrona debe ser Future<T>
Future<String> fetchUserName(int id) async {
  // await espera a que el Future se complete y obtiene su valor
  // Durante la espera, la función actual se pausa, pero no bloquea la ejecución de otros códigos
  await Future.delayed(Duration(seconds: 1));
  return 'Usuario$id';
}

Future<int> fetchUserAge(int id) async {
  await Future.delayed(Duration(milliseconds: 500));
  return 20 + id;
}

// Ejecutar varias operaciones asíncronas en orden
Future<void> printUserInfo(int id) async {
  print('Comenzando a obtener información del usuario...');

  // Esperar una por una: primero obtener el nombre, luego la edad
  var name = await fetchUserName(id);
  print('Nombre obtenido: $name');

  var age = await fetchUserAge(id);
  print('Edad obtenida: $age');

  print('Usuario EJEMPLO: $name, edad: $age');
}

// Ejecutar varias operaciones asíncronas de forma concurrente
Future<void> printUserInfoParallel(int id) async {
  print('Comenzando a obtener información del usuario de forma concurrente...');

  // Lanzar dos solicitudes al mismo tiempo sin esperarse mutuamente
  var nameFuture = fetchUserName(id);
  var ageFuture = fetchUserAge(id);

  // Esperar a que ambas solicitudes se completen
  var results = await Future.wait([nameFuture, ageFuture]);
  var name = results[0] as String;
  var age = results[1] as int;

  print('Usuario EJEMPLO (concurrente): $name, edad: $age');
}

void main() async {
  // Ejecución secuencial (tiempo total ≈ 1s + 0.5s = 1.5s)
  await printUserInfo(1);

  print('---');

  // Ejecución concurrente (tiempo total ≈ max(1s, 0.5s) = 1s)
  await printUserInfoParallel(2);
}
开始获取用户信息...
获取到名称: 用户1
获取到年龄: 21
EJEMPLO 用户: 用户1, 年龄: 21
---
开始并发获取用户信息...
EJEMPLO 用户(并发): 用户2, 年龄: 22

Si varias operaciones asíncronas no tienen dependencias entre sí, se debe usar Future.wait para que se ejecuten de forma concurrente en lugar de usar await una por una. Esto mejora significativamente el rendimiento: el tiempo total es igual al de la operación más lenta, no a la suma de todas las operaciones.

Reglas fundamentales de async/await

ReglaDescripción
Las funciones async devuelven FutureIncluso si la función devuelve T, con async se envuelve automáticamente como Future<T>
await solo puede usarse dentro de funciones asyncLas funciones normales no pueden usar await
await pausa la función actualPero no bloquea la ejecución de otros códigos
await obtiene el resultado de FutureSi Future tiene un error, await lanzará una excepción

Llamadas en cadena con then / catchError

Además de async/await, Dart admite llamadas en cadena usando then() y catchError().

Se trata del estilo tradicional basado en Promise.

Ejemplo

// Función que simula una solicitud de red
Future<String> fetchData(String url) {
  return Future.delayed(Duration(seconds: 1), () {
    if (url.isEmpty) {
      throw Exception('La URL no puede estar vacía');
    }
    return 'Datos de $url';
  });
}

void main() {
  print('Comenzando la solicitud...');

  // Llamadas en cadena con then / catchError
  fetchData('https://example.com/api')
      .then((data) {
        // Solicitud exitosa
        print('Datos obtenidos: $data');
        return 'Procesado: $data';  // El valor de retorno se pasa al siguiente then
      })
      .then((processed) {
        // Procesar el valor de retorno del then anterior
        print(processed);
      })
      .catchError((error) {
        // Capturar de manera unificada cualquier error en la cadena
        print('Error en la solicitud: $error');
      })
      .whenComplete(() {
        // Se ejecuta tanto en éxito como en fracaso (similar a finally)
        print('Solicitud finalizada (sea éxito o fracaso)');
      });

  print('La solicitud se ha enviado, el programa continúa...');
}
开始请求...
请求已发出,程序继续...
获取到数据: 来自 https://example.com/api 的数据
处理后的: 来自 https://example.com/api 的数据
请求结束(无论成功或失败)

async/await vs then/catchError: ¿cómo elegir?

EscenarioMétodo recomendadoMotivo
Flujo asíncrono linealasync/awaitEl código es tan claro como el síncrono
Varias solicitudes concurrentesasync/await + Future.waitSemántica clara
Transformación de datos en cadenathen()Cada then hace una transformación, la cadena es clara
Un solo callbackthen()Más conciso que async/await

En la mayoría de los casos se recomienda async/await porque su legibilidad es mejor. Sin embargo, then() sigue siendo valioso en procesamientos en cadena simples. Ambos pueden combinarse, pero no se recomienda: una función debe usar o bien async/await o bien then/catchError; mezclarlos hace que el código sea difícil de entender.


Manejo de errores asíncronos

En las operaciones asíncronas pueden ocurrir errores, por lo que es necesario manejarlos con try-catch.

async/await hace que el manejo de errores sea tan simple como en el código síncrono.

Ejemplo

// Simular una solicitud de red que puede fallar
Future<String> fetchUserProfile(int userId) async {
  await Future.delayed(Duration(seconds: 1));

  if (userId <= 0) {
    throw Exception('ID de usuario no válido: $userId');
  }

  if (userId == 404) {
    throw HttpException('El usuario no existe');
  }

  return 'Perfil del usuario $userId';
}

// Excepción personalizada
class HttpException implements Exception {
  final String message;
  HttpException(this.message);

  @override
  String toString() => 'HttpException: $message';
}

Future<void> loadUserProfile(int userId) async {
  print('Cargando el perfil del usuario $userId...');

  try {
    var profile = await fetchUserProfile(userId);
    print('Carga exitosa: $profile');
  } on HttpException catch (e) {
    // Capturar errores asíncronos específicos por tipo
    print('Error HTTP: $e');
    print('Sugerencia al usuario: verifique si el usuario existe');
  } on Exception catch (e) {
    // Capturar otras excepciones
    print('Error general: $e');
  } finally {
    print('Operación de carga finalizada');
  }
}

void main() async {
  await loadUserProfile(1);    // Éxito
  print('---');
  await loadUserProfile(404);  // HttpException
  print('---');
  await loadUserProfile(-1);   // Excepción general
}
正在加载用户 1 的资料...
加载成功: 用户 1 的个人资料
加载操作结束
---
正在加载用户 404 的资料...
HTTP 错误: HttpException: 用户不存在
提示用户:请检查用户是否存在
加载操作结束
---
正在加载用户 -1 的资料...
一般错误: Exception: 无效的用户 ID: -1
加载操作结束

Reglas de manejo de errores en funciones async:

  • Si se lanza una excepción en una función async, esta se envuelve automáticamente en un Future
  • El llamador puede capturar la excepción de await con try-catch
  • Si se usa then(), la excepción se pasa a catchError()
  • Las excepciones asíncronas no capturadas no provocan el bloqueo del programa, pero se consideran errores de Future no manejados

Nunca ignore los errores asíncronos. Cada llamada a una función async debe tener su manejo de errores correspondiente. Los errores asíncronos no manejados imprimen advertencias en modo de desarrollo y pueden ignorarse silenciosamente en producción, lo que provoca errores difíciles de depurar.

Otras extensiones