Diferencia entre revisiones de «Flutter DART Programación asíncrona»

De MediaWiki
Ir a la navegación Ir a la búsqueda
 
(No se muestran 6 ediciones intermedias del mismo usuario)
Línea 1: Línea 1:
 
==Introducción==
 
==Introducción==
  
−
* Más información: https://dart.dev/codelabs/async-await
+
* Más información:
 +
:* https://dart.dev/language/async
 +
:* https://dart.dev/libraries/async/async-await
 +
:* https://dart.dev/language/concurrency
 +
 
 +
* La programación asíncrona permite iniciar operaciones que pueden tardar un tiempo en completarse, como una petición HTTP, la lectura de un fichero o el acceso a una base de datos, '''sin bloquear la ejecución del programa mientras se espera por el resultado'''.
 +
 
 +
* En Dart, la programación asíncrona se basa principalmente en:
 +
:* '''Future''': representa un resultado que estará disponible en el futuro.
 +
:* '''async / await''': permiten escribir código asíncrono con una estructura similar al código síncrono.
 +
:* '''Stream''': permite recibir una secuencia de valores de forma asíncrona.
 +
 
 +
 
 +
 
 +
<br />
 +
* Es importante distinguir '''asincronía''' de '''paralelismo'''.
 +
 
 +
: Un Future '''no implica que el código se ejecute automáticamente en un hilo diferente'''.
  
−
* La programación asíncrona hace referencia al uso de [https://wiki.cifprodolfoucha.es/index.php?title=Programaci%C3%B3n_de_dispositivos_m%C3%B3biles#UNIDADE_9:_Threads_e_AsyncTask threads] para ejecutar en modo 'paralelo' al hilo principal otra serie de tareas.
+
: Normalmente el código Dart se ejecuta dentro de un '''isolate''' que dispone de su propio '''event loop''' y su propia zona de memoria. Mientras una operación asíncrona está esperando por una operación externa (como un Future), el event loop puede continuar procesando otros eventos. Cada isolate emplea su propio hilo de ejecución.
  
 +
: Si necesitamos ejecutar cálculos intensivos realmente en paralelo, Dart permite utilizar otros '''isolates'''. Para tareas puntuales puede emplearse, por ejemplo, <code>Isolate.run()</code>.
  
 
<br />
 
<br />
Línea 10: Línea 28:
 
==Futures==
 
==Futures==
  
−
* Más información:  
+
* Más información:
−
:* https://dart.dev/guides/language/language-tour#handling-futures
+
:* https://api.dart.dev/dart-async/Future-class.html
−
:* https://dart.dev/guides/libraries/library-tour#future
+
:* https://dart.dev/libraries/dart-async
−
:* https://dart.dev/guides/libraries/futures-error-handling
+
:* https://dart.dev/libraries/async/async-await
 +
:* https://dart.dev/libraries/async/futures-error-handling
  
 +
* Un objeto <code>Future&lt;T&gt;</code> representa el resultado de una operación asíncrona que podrá completarse posteriormente:
 +
:* con un valor de tipo <code>T</code>, si la operación finaliza correctamente (por ejemplo, llamamos a una lectura en disco de un archivo y va a devolver una cadena en un futuro);
 +
:* con un error, si la operación falla.
  
 +
* Cuando una función inicia una operación asíncrona normalmente devuelve inmediatamente un <code>Future</code>. El programa puede continuar ejecutando otras tareas mientras espera a que ese Future se complete.
  
 
<center>
 
<center>
Línea 21: Línea 44:
 
</center>
 
</center>
  
 +
<br />
  
−
<br />
+
:<syntaxhighlight lang="dart" enclose="div" highlight="5,12">
−
:<syntaxhighlight lang="java" enclose="div" highlight="5,13" >
 
 
void main() {
 
void main() {
  
 
   print('Inicia programa');
 
   print('Inicia programa');
−
 
+
 
 
   getImagenes('imagenes/animales').then(
 
   getImagenes('imagenes/animales').then(
−
     (datos) => print(datos)
+
     (datos) => print(datos),
 
   );
 
   );
−
 
+
 
 
   print('Fin programa');
 
   print('Fin programa');
−
 
 
 
}
 
}
  
−
Future<List<String>> getImagenes(String path){
+
Future<List<String>> getImagenes(String path) {
−
 
+
 
−
   List<String> datos = ['1.jpg','2.jpg','3.jpg','4.jpg'];
+
   final datos = ['1.jpg', '2.jpg', '3.jpg', '4.jpg'];
−
 
+
 
−
 
+
   return Future.delayed(
−
   return Future.delayed(Duration(seconds: 4),() => datos); // Paramos el hilo durante 4 segundos
+
    const Duration(seconds: 4),
−
 
+
    () => datos,
−
    
+
  );
 +
 
 +
   // También podríamos utilizar:
 
   // return Future.value(datos);
 
   // return Future.value(datos);
−
 
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 +
* <code>Future.delayed()</code> '''no detiene ni bloquea el hilo durante cuatro segundos'''.
 +
 +
: Lo que hace es crear un Future cuya operación se ejecutará después de que transcurra, como mínimo, el tiempo indicado. Mientras tanto, el event loop puede continuar procesando otras tareas.
 +
 +
* En versiones actuales de Dart también existe:
 +
 +
:<syntaxhighlight lang="dart">
 +
await Future.pause(const Duration(seconds: 4));
 +
</syntaxhighlight>
 +
 +
: <code>Future.pause()</code> resulta apropiado cuando únicamente queremos esperar un determinado intervalo de tiempo y no necesitamos ejecutar ningún callback asociado al temporizador.
  
  
 
<br />
 
<br />
 +
 
===Gestión de errores===
 
===Gestión de errores===
 +
 +
* Cuando trabajamos directamente con la API de <code>Future</code>, disponemos principalmente de dos formas de tratar los errores:
 +
:* mediante el parámetro <code>onError</code> de <code>then()</code>;
 +
:* mediante el método <code>catchError()</code>.
 +
  
 
<center>
 
<center>
Línea 58: Línea 98:
 
</center>
 
</center>
  
−
:<syntaxhighlight lang="java" enclose="div" highlight="8,11,24,26" >
+
 
 +
 
 +
<br />
 +
 
 +
====Empleando onError====
 +
 
 +
* El método <code>then()</code> permite definir:
 +
:* una función que se ejecutará si el Future termina correctamente;
 +
:* opcionalmente, una función <code>onError</code> que se ejecutará si el Future termina con un error.
 +
 
 +
:<syntaxhighlight lang="dart">
 
void main() {
 
void main() {
  
−
   print('Inicia programa');
+
   getImages('imagenes/animales').then(
−
 
+
    (datos) {
−
  try {
+
       datos.forEach((elem) => print('Dato: $elem'));
−
    getImages('imagenes/animales').then((datos) {
+
    },
−
       datos.forEach((elem) => print('Dato:$elem'));
+
     onError: (error) {
−
    }).catchError((e) { print(e);} );
+
      print('Error: $error');
−
      
+
    },
−
  }
+
   );
−
  catch(e){
 
−
    print('Excep:$e');
 
−
  }
 
−
   print('Fin programa');
 
−
 
 
 
}
 
}
  
−
Future<List<String>> getImages(String path){
+
Future<List<String>> getImages(String path) {
−
 
+
 
−
  List<String> imagenes = ['1.jpg','2.jpg','3.jpg'];
+
   return Future.delayed(
−
 
+
    const Duration(seconds: 3),
−
  // Búsqueda de imágenes a partir del path
+
    () => throw Exception('Error cargando imágenes'),
−
 
+
  );
−
  //throw('Exepción no controlada');
 
−
 
 
−
   return Future.delayed(Duration(seconds: 3),() => Future.error('Error controlado'));
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
−
<u>Nota:</u> Una de las diferencias entre catchError y onError es que con catchError gestionamos las excepciones que se puedan lanzar dentro del then.
+
* En este caso:
 +
:* si <code>getImages()</code> termina correctamente, se ejecuta el primer callback de <code>then()</code>;
 +
:* si <code>getImages()</code> termina con un error, se ejecuta <code>onError</code>.
  
 +
'''Importante:''' <code>onError</code> gestiona el error con el que llega el Future a <code>then()</code>.
  
 +
Si se produce un nuevo error dentro del callback de éxito de <code>then()</code>, ese error pertenece al Future devuelto por <code>then()</code> y no será gestionado por ese mismo <code>onError</code>.
  
−
* Empleando esta forma de capturar errores, dentro del Future, si se lanza una excepción, no capturaremos el error con catchError ni con onError.
+
Por ejemplo:
−
: Tendríamos que emplear try-catch dentro del Future o fuera, antes de la llamada como el ejemplo anterior.
 
−
: Ese tipo de capturas, funciona si dentro del Future se llama a una función que a su vez es otro Future (por ejemplo, lectura de un fichero de forma asíncrona) y ese segundo Future lanza una excepción.
 
  
−
: '''Existe otra alternativa, que es emplear <u>un Future.sync</u> de esta forma:
+
:<syntaxhighlight lang="dart">
−
:<syntaxhighlight lang="java" enclose="div" highlight="7,15" >
+
miFuture().then(
 +
  (valor) {
 +
    throw Exception('Error producido dentro del then');
 +
  },
 +
  onError: (error) {
 +
    print('Error del Future original: $error');
 +
  },
 +
);
 +
</syntaxhighlight>
 +
 
 +
En este caso, <code>onError</code> no gestiona la excepción producida dentro del callback de éxito.
 +
 
 +
 
 +
<br />
 +
 
 +
====Empleando catchError====
 +
 
 +
* Otra posibilidad consiste en añadir <code>catchError()</code> después de <code>then()</code>:
 +
 
 +
:<syntaxhighlight lang="dart">
 
void main() {
 
void main() {
  
−
   print('Inicia programa');
+
   getImages('imagenes/animales')
 +
      .then((datos) {
 +
        datos.forEach((elem) => print('Dato: $elem'));
 +
      })
 +
      .catchError((error) {
 +
        print('Error: $error');
 +
      });
 +
}
 +
</syntaxhighlight>
 +
 
 +
* <code>catchError()</code> permite capturar:
 +
:* el error con el que se haya completado el Future original;
 +
:* errores producidos dentro de los callbacks <code>then()</code> anteriores de la cadena.
 +
 
 +
Por ejemplo:
 +
 
 +
:<syntaxhighlight lang="dart">
 +
miFuture()
 +
    .then((valor) {
 +
      print(valor);
 +
 
 +
      throw Exception('Error dentro del then');
 +
    })
 +
    .catchError((error) {
 +
      print('Error capturado: $error');
 +
    });
 +
</syntaxhighlight>
 +
 
 +
En este caso, <code>catchError()</code> también captura la excepción producida dentro de <code>then()</code>.
 +
 
 +
 
 +
<br />
 +
 
 +
====Diferencia entre onError y catchError====
 +
 
 +
Podemos resumir su comportamiento de esta forma:
 +
 
 +
{| class="wikitable"
 +
! Mecanismo
 +
! Error del Future original
 +
! Error producido dentro de then()
 +
|-
 +
| <code>then(..., onError: ...)</code>
 +
| Sí
 +
| No, si se produce dentro del callback de éxito de ese mismo <code>then()</code>
 +
|-
 +
| <code>then(...).catchError(...)</code>
 +
| Sí
 +
| Sí
 +
|}
 +
 
 +
Por ello, generalmente resulta más sencillo utilizar:
 +
 
 +
:<syntaxhighlight lang="dart">
 +
future
 +
    .then(procesarResultado)
 +
    .catchError(procesarError);
 +
</syntaxhighlight>
 +
 
 +
que:
 +
 
 +
:<syntaxhighlight lang="dart">
 +
future.then(
 +
  procesarResultado,
 +
  onError: procesarError,
 +
);
 +
</syntaxhighlight>
 +
 
 +
No obstante, <code>onError</code> sigue siendo útil cuando necesitamos tratar de forma diferente el error procedente del Future original y los posibles errores producidos posteriormente en la cadena.
 +
 
  
−
  getImages('imagenes/animales').then((datos) {
+
<br />
−
    datos.forEach((elem) => print('Dato:$elem'));
 
−
  }).catchError((e) => print("Hoube un erro:$e"));
 
  
−
  print('Fin programa');
+
====Forma recomendada con async / await====
  
−
}
+
* Cuando utilizamos <code>async</code> y <code>await</code>, normalmente resulta más sencillo emplear <code>try-catch</code>:
  
−
Future<List<String>> getImages(String path){
+
:<syntaxhighlight lang="dart">
 +
Future<void> main() async {
  
−
   return Future.sync(() {
+
   try {
−
    List<String> imagenes = ['1.jpg','2.jpg','3.jpg'];
 
  
−
     // Búsqueda de imágenes a partir del path
+
     final datos = await getImages('imagenes/animales');
  
−
     throw('Exepción no controlada');
+
     print(datos);
  
−
    return Future.delayed(Duration(seconds: 3),() => Future.value(imagenes));
+
  } catch (error) {
  
−
   });
+
    print('Error: $error');
 +
   }
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 +
* Actualmente esta forma suele ser la más sencilla y legible para la mayor parte del código asíncrono.
  
−
<br />
+
===Estado de un Future===
 +
 
 +
* Un Future puede encontrarse en dos estados:
 +
:* '''Incompleto (uncompleted):''' todavía no dispone de un resultado.
 +
:* '''Completo (completed):''' la operación ha terminado.
 +
 
 +
* Cuando un Future se completa puede hacerlo:
 +
:* con un '''valor''';
 +
:* con un '''error'''.
 +
 
 +
* El callback principal de <code>then()</code> se ejecuta cuando el Future se completa correctamente con un valor.
  
−
===Estado Completo===
+
* El método <code>whenComplete()</code> se ejecuta independientemente de que el Future haya terminado con un valor o con un error.
−
* Un Future puede tener dos estados:
 
−
:* Incompleto: Cuando se está ejecutando.
 
−
:* Completo: Cuando ha terminado de ejecutarse. Este estado lo va a alcanzar haya o no error durante la ejecución del Future.
 
  
 +
: Su comportamiento es similar al bloque <code>finally</code> de un <code>try-catch-finally</code>.
  
−
* Es decir, el método [https://api.dart.dev/stable/2.2.0/dart-async/Future/then.html then] se va a ejecutar sólo cuando el Future acabe de ejecutarse correctamente mientras que el [https://api.dart.dev/stable/2.2.0/dart-async/Future/whenComplete.html método whenComplete] se ejecuta tanto si ha ido bien como si ha habido un error.
+
* Más información:
 +
:* https://api.dart.dev/dart-async/Future/then.html
 +
:* https://api.dart.dev/dart-async/Future/whenComplete.html
  
−
: Veamos un ejemplo:
+
:<syntaxhighlight lang="dart" enclose="div">
−
:<syntaxhighlight lang="java" enclose="div" highlight="5,6,8-11" >
 
 
void main() {
 
void main() {
  
 
   print('Inicia programa');
 
   print('Inicia programa');
−
 
 
−
  cargarImagenes().then((datos) => print('Acabó el Future1 sin errores')).
 
−
      whenComplete(() => print('Terminó Future1'));
 
  
−
   cargarImagenesConError().
+
  cargarImagenes()
−
    then((datos) => print('Acabó el Future2 sin errores')).
+
      .then((_) => print('Acabó el Future1 sin errores'))
−
    catchError((e) { print('Capturando el error:$e'); }).
+
      .whenComplete(() => print('Terminó Future1'));
−
    whenComplete(() => print('Terminó Future2'));
+
 
 +
   cargarImagenesConError()
 +
      .then((_) => print('Acabó el Future2 sin errores'))
 +
      .catchError((error) {
 +
        print('Capturando el error: $error');
 +
      })
 +
      .whenComplete(() => print('Terminó Future2'));
  
 
   print('Fin programa');
 
   print('Fin programa');
−
 
 
 
}
 
}
−
Future<void> cargarImagenesConError(){
+
 
−
    
+
Future<void> cargarImagenesConError() {
−
return Future.delayed(Duration(seconds: 2),() => throw('Error cargando imágenes'));  
+
 
−
 
+
   return Future.delayed(
 +
    const Duration(seconds: 2),
 +
    () => throw Exception('Error cargando imágenes'),
 +
  );
 
}
 
}
  
−
Future<void> cargarImagenes(){
+
Future<void> cargarImagenes() {
−
    
+
 
−
return Future.delayed(Duration(seconds: 2),() { print('Cargamos imágenes');});  
+
   return Future.delayed(
−
 
+
    const Duration(seconds: 2),
 +
    () => print('Cargamos imágenes'),
 +
  );
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
−
:* El Future cargarImagenes no devuelva nada (void) y se ejecuta sin errores, por lo tanto, la llamada a la función desde el hilo principal ejecuta el método then y el método whenComplete.
+
:* <code>cargarImagenes()</code> se completa correctamente. Por lo tanto se ejecutan <code>then()</code> y posteriormente <code>whenComplete()</code>.
−
:* El Future cargarImagenesConError no devuelva nada (void) y <u>se ejecuta con errores</u>, por lo tanto, la llamada a la función desde el hilo principal ejecuta el método catchError y el método whenComplete.  
 
  
 +
:* <code>cargarImagenesConError()</code> se completa con un error. Por lo tanto no se ejecuta el callback de éxito de <code>then()</code>, el error es tratado por <code>catchError()</code> y finalmente se ejecuta <code>whenComplete()</code>.
  
  
 
<br />
 
<br />
 +
 
==Async/Await==
 
==Async/Await==
  
 
* Más información:
 
* Más información:
−
:* https://dart.dev/codelabs/async-await
+
:* https://dart.dev/language/async
−
:* https://api.flutter.dev/flutter/dart-async/dart-async-library.html
+
:* https://dart.dev/libraries/async/async-await
 +
:* https://api.dart.dev/dart-async/Future-class.html
 +
 
 +
* En los ejemplos anteriores hemos utilizado <code>then()</code> para registrar una función que se ejecutará cuando un Future se complete correctamente.
 +
 
 +
* Sin embargo, Dart proporciona las palabras clave <code>async</code> y <code>await</code>, que permiten escribir código asíncrono con una estructura mucho más similar a la del código síncrono.
 +
 
 +
* Es importante recordar que '''los Future no se ejecutan necesariamente en otro hilo'''.
 +
 
 +
: Dentro de un isolate, Dart utiliza un '''event loop''' para procesar los eventos y operaciones asíncronas.
 +
 
 +
* Cuando utilizamos <code>await</code>, '''no bloqueamos el hilo ni el isolate'''.
  
−
* En los ejemplos anteriores vimos que para llamar a un Future y obtener el resultado disponemos del '''método then'''. Dentro del método podemos definar una función donde podemos trabajar con los datos que son enviados desde el Future.
+
: Lo que se suspende temporalmente es la ejecución de la función <code>async</code> actual. Mientras se espera a que el Future se complete, el event loop puede continuar procesando otras tareas.
−
: Todos los Future son ejecutados en un hilo separado del principal, de tal forma, que cuando tenga tiempo, irá ejecutando los Futures pendientes.
 
  
−
* Por lo tanto si queremos hacer uso de los datos del Future necesitamos implementar el 'método then', pero entonces <u>estaríamos obligados</u> a tener toda la lógica del programa que tenga que ver con los datos recibidos, dentro del then ya que fuera del mismo no podemos saber cuando disponemos de los datos o no.
 
−
:<syntaxhighlight lang="java" enclose="div" highlight="7,8" >
 
−
void main() {
 
  
−
   print('Inicia programa');  
+
<br />
−
   List<String>? datos;
+
 
−
 
+
===Uso de await===
−
  getImages('imagenes/animales').then((datos) {
+
 
−
      print(datos);   
+
Para obtener el resultado de un Future podemos utilizar <code>await</code>:
−
  });
+
 
−
     
+
:<syntaxhighlight lang="dart" enclose="div" highlight="1,5">
 +
Future<void> main() async {
 +
 
 +
   print('Inicia programa');
 +
 
 +
   final datos = await getImages('imagenes/animales');
 +
 
 +
  print('Datos obtenidos después de ejecutar el Future: $datos');
 +
 
 
   print('Fin programa');
 
   print('Fin programa');
−
 
 
 
}
 
}
−
</syntaxhighlight>  
+
 
 +
Future<List<String>> getImages(String path) {
 +
 
 +
  final imagenes = ['1.jpg', '2.jpg', '3.jpg'];
 +
 
 +
  // Búsqueda de imágenes a partir del path.
 +
 
 +
  return Future.delayed(
 +
    const Duration(seconds: 3),
 +
    () => imagenes,
 +
  );
 +
}
 +
</syntaxhighlight>
 +
 
 +
* Para utilizar <code>await</code>:
 +
:* la llamada debe encontrarse dentro de una función marcada como <code>async</code>;
 +
:* normalmente la expresión que sigue a <code>await</code> devuelve un Future.
 +
 
 +
* Si tenemos:
 +
 
 +
:<syntaxhighlight lang="dart">
 +
Future<List<String>> future = getImages('imagenes/animales');
 +
</syntaxhighlight>
 +
 
 +
: la variable contiene el propio objeto <code>Future&lt;List&lt;String&gt;&gt;</code>.
 +
 
 +
* En cambio:
 +
 
 +
:<syntaxhighlight lang="dart">
 +
List<String> datos = await getImages('imagenes/animales');
 +
</syntaxhighlight>
 +
 
 +
: <code>await</code> suspende la función actual hasta que el Future se completa y la expresión produce el valor contenido en el Future.
 +
 
 +
'''Nota importante:''' <code>await</code> no hace que el Future deje de ser asíncrono ni bloquea el programa completo. Solo suspende temporalmente la función <code>async</code> en la que aparece.
  
  
 +
<br />
  
−
* En algunos casos puede ser necesario que el hilo 'espere' hasta que el Future devueva el resultado (por ejemplo, dentro de un Future, ejecutar una función que sea otro Future, como leer un archivo).
+
===Funciones async===
−
: Para lograr esto debemos:
 
−
:* Llamar al future con la palabra clave '''await''' antes del nombre de la función. Esto hará que la función marcada como Future <u>devuelva el dato</u>.
 
−
:* Definir la función desde donde se está llamando como '''async'''. Si la función que tiene el async devuelve algo, tendrá que devolver un Future con el tipo de dato que vaya a devolver.
 
  
 +
* Una función marcada como <code>async</code> devuelve un Future.
  
−
:<syntaxhighlight lang="java" enclose="div" highlight="1,5" >
+
Si queremos devolver un <code>String</code>:
−
void main() async {  // Definimos como async ya que va a hacer una llamada con await a un Future
 
  
−
  print('Inicia programa');
+
:<syntaxhighlight lang="dart">
−
 
+
Future<String> obtenerDato() async {
−
  final datos = await getImages('imagenes/animales');  // Esperamos a que termine de ejecutarse.
+
 
−
     
+
   return 'Dato obtenido';
−
  print('Datos obtenidos después de ejecutar el Future:$datos'); 
 
−
   print('Fin programa');
 
−
 
 
 
}
 
}
 +
</syntaxhighlight>
 +
 +
Aunque dentro de la función devolvamos un <code>String</code>, la llamada devuelve:
 +
 +
:<syntaxhighlight lang="dart">
 +
Future<String>
 +
</syntaxhighlight>
 +
 +
Dart se encarga automáticamente de completar el Future con el valor retornado.
 +
 +
Por ello no es necesario escribir:
 +
 +
:<syntaxhighlight lang="dart">
 +
return Future.value(resultado);
 +
</syntaxhighlight>
 +
 +
y podemos utilizar directamente:
 +
 +
:<syntaxhighlight lang="dart">
 +
return resultado;
 +
</syntaxhighlight>
  
−
Future<List<String>> getImages(String path) {
+
 
−
 
+
<br />
−
   List<String> imagenes = ['1.jpg','2.jpg','3.jpg'];
+
 
−
 
+
===Ejemplo con lectura de un fichero===
−
   // Búsqueda de imágenes a partir del path
+
 
−
 
+
:<syntaxhighlight lang="dart" enclose="div">
−
 
+
Future<String> getDatoLocal(String path) async {
−
  return Future.delayed(Duration(seconds: 3),() => imagenes);
+
 
−
    
+
   final file = File(path);
 +
 
 +
   final resultado = await file.readAsString();
 +
 
 +
   return resultado;
 
}
 
}
−
</syntaxhighlight>  
+
</syntaxhighlight>
 +
 
 +
* <code>readAsString()</code> devuelve un <code>Future&lt;String&gt;</code>.
 +
 
 +
* Como queremos trabajar con el contenido una vez finalizada la lectura utilizamos <code>await</code>.
 +
 
 +
* Al estar utilizando <code>await</code>, la función debe estar marcada como <code>async</code>.
 +
 
 +
* Aunque devolvemos directamente <code>resultado</code>, el tipo de retorno de la función es <code>Future&lt;String&gt;</code>.
  
−
: '''<u>Nota importante:</u>''' Fijarse que al emplear 'await' esto hace que se devuelva el dato de retorno del Future. Si intentamos hacer esta asignación directamente lo que tendríamos sería un Future.
+
Si no necesitásemos realizar ninguna operación con el resultado antes de devolverlo, incluso podríamos simplificar la función:
  
 +
:<syntaxhighlight lang="dart">
 +
Future<String> getDatoLocal(String path) {
  
 +
  return File(path).readAsString();
 +
}
 +
</syntaxhighlight>
  
  
 
<br />
 
<br />
−
* Dentro del Future, si a este le indicamos que va a ser async (porque dentro del método llamamos a otro Future) ese otro Future ya va a devolver un Future.value, por lo que no hace falta devolver un return Future.value(valor devuelto por el future) y podremos devolver diretamente el dato con return 'Valor devuelto por el future'.
 
−
: Por ejemplo:
 
  
−
:<syntaxhighlight lang="java" enclose="div" highlight="" >
+
===Gestión de errores con async / await===
−
Future<String> getDatoLocal(String path) async{
 
  
−
  // Por ejemplo path apunto a un archivo local => 'C:\\temporal\\hosts'
+
* Actualmente, para la mayoría de los casos, la forma más sencilla de gestionar errores asíncronos es utilizar <code>await</code> junto con <code>try-catch</code>.
−
  File file = File(path);
 
−
  var resultado = await file.readAsString();  // readAsString es un Future. Como queremos que espere el resultado, modificamos el Future de getDatoLocal con async
 
  
−
   return resultado; // Ahora ya no hace falta devolver un Future.value(resultado)
+
:<syntaxhighlight lang="dart">
 +
Future<void> main() async {
 +
 
 +
  try {
 +
 
 +
    final datos = await getImages('imagenes/animales');
 +
 
 +
    print(datos);
 +
 
 +
   } catch (error, stackTrace) {
 +
 
 +
    print('Error: $error');
 +
    print(stackTrace);
 +
  }
 
}
 
}
−
</syntaxhighlight>  
+
</syntaxhighlight>
  
 +
* Si el Future esperado mediante <code>await</code> se completa con un error, dicho error se comportará en ese punto como una excepción y podrá ser capturado mediante <code>try-catch</code>.
  
 +
* También podemos utilizar <code>finally</code>:
 +
 +
:<syntaxhighlight lang="dart">
 +
try {
 +
 +
  final datos = await getImages('imagenes/animales');
 +
  print(datos);
 +
 +
} catch (error) {
 +
 +
  print('Error: $error');
 +
 +
} finally {
 +
 +
  print('Operación finalizada');
 +
}
 +
</syntaxhighlight>
 +
 +
: Este patrón equivale conceptualmente a utilizar <code>then()</code>, <code>catchError()</code> y <code>whenComplete()</code>, pero suele resultar más sencillo de leer.
  
  
 
<br />
 
<br />
 +
 
===Ejercicios propuestos===
 
===Ejercicios propuestos===
  
−
'''Ejercicio 1:''' Sabrías aplicar los conceptos aprendidos para que el pedido apareza correctamente ([https://dart.dev/codelabs/async-await ejercicio obtenido de este enlace]):
+
'''Ejercicio 1:'''
−
:<syntaxhighlight lang="java" enclose="div" highlight="" >
+
 
 +
Sabrías aplicar los conceptos aprendidos para que el pedido aparezca correctamente:
 +
 
 +
:<syntaxhighlight lang="dart">
 
String createOrderMessage() {
 
String createOrderMessage() {
 +
 
   var order = fetchUserOrder();
 
   var order = fetchUserOrder();
 +
 
   return 'Tu pedido es: $order';
 
   return 'Tu pedido es: $order';
 
}
 
}
  
 
Future<String> fetchUserOrder() =>
 
Future<String> fetchUserOrder() =>
−
    // Imagine that this function is
 
−
    // more complex and slow.
 
 
     Future.delayed(
 
     Future.delayed(
 
       const Duration(seconds: 2),
 
       const Duration(seconds: 2),
Línea 271: Línea 533:
  
 
void main() {
 
void main() {
 +
 
   print('Preparando el pedido...');
 
   print('Preparando el pedido...');
 +
 
   print(createOrderMessage());
 
   print(createOrderMessage());
 +
 
   print('Pedido finalizado...');
 
   print('Pedido finalizado...');
 
}
 
}
−
</syntaxhighlight>  
+
</syntaxhighlight>
  
 +
Modifica el programa utilizando <code>async</code> y <code>await</code> para que primero se espere a obtener el pedido y posteriormente se muestre el mensaje.
  
  
 
<br />
 
<br />
−
'''Ejercicio 2:'''
 
  
 +
'''Ejercicio 2:'''
 +
 +
'''Apartado A)'''
  
−
: '''Apartado A)'''
+
* Se necesita mostrar cuál es el premio que recibe un alumno en función de la edad que tenga.
−
* Se necesita mostrar cual es el premio que recibe un alumno en base a la edad que tenga.
 
−
: Crea un método obtenerEdad al que se le envíe un nombre y busque en un mapa (defínelo previamente con algún dato), la edad que se corresponde con dicho nombre.
 
−
: Si el nombre no existe en el mapa, debe devolver el valor 0.
 
  
−
: Crea un método de nombre obtenerPremio al que se le envia una edad y devuelve en base a una lista, el premio que le corresponde a esa edad. Utiliza el operador 'resto' para dividir la edad entre el número de regalos y que siempre devuelve un número dentro del rango de la lista.
+
* Crea un método <code>obtenerEdad</code> al que se le envíe un nombre y que busque en un mapa la edad correspondiente a dicho nombre.
  
−
: Desde la función main, llama a obtenerEdad y el resultado empléalo para llamar a obtenerPremio y muestra el premio.
+
* Si el nombre no existe en el mapa debe devolver el valor 0.
  
 +
* Crea un método <code>obtenerPremio</code> al que se le envíe una edad y que devuelva, basándose en una lista, el premio correspondiente.
  
 +
* Utiliza el operador resto (<code>%</code>) para obtener siempre una posición válida dentro de la lista de premios.
  
 +
* Desde la función <code>main</code>, llama a <code>obtenerEdad</code> y utiliza el resultado para llamar posteriormente a <code>obtenerPremio</code>.
  
−
: '''Apartado B)'''
 
−
* Ahora supón que los dos métodos acceden a Internet y son ejecutados en un hilo separado. Haz lo necesario para que todo siga funcionando igual.
 
−
: Haz que el método obtenerEdad tarde 4 segundos en ejecutarse y que el método obtenerPremio tarde 2 segundos.
 
  
 +
'''Apartado B)'''
  
 +
* Ahora supón que ambos métodos realizan operaciones asíncronas, por ejemplo accesos a un servidor a través de Internet.
  
 +
* Modifica las funciones para que devuelvan un Future y utiliza <code>async</code> y <code>await</code> para mantener el orden correcto de ejecución.
  
 +
* Simula que <code>obtenerEdad</code> tarda 4 segundos en obtener el resultado y que <code>obtenerPremio</code> tarda 2 segundos.
 +
 +
'''Importante:''' Estas operaciones son asíncronas, pero eso no significa que Dart cree automáticamente un hilo diferente para cada una.
  
  
 
<br />
 
<br />
 +
 
====Solución Ejercicio 2====
 
====Solución Ejercicio 2====
 +
 
'''APARTADO A)'''
 
'''APARTADO A)'''
−
:<syntaxhighlight lang="java" enclose="div" highlight="" >
+
 
−
int obtenerEdad(String nombre){
+
:<syntaxhighlight lang="dart">
−
 
+
int obtenerEdad(String nombre) {
−
  // ACCEDEMOS A INTERNET A BUSCAR LA EDAD
+
 
−
   final alumnos = {'pedro' : 27, 'ana' : 35, 'luisa' : 22};
+
   final alumnos = {
−
    
+
    'pedro': 27,
−
  if (!alumnos.containsKey(nombre)) return 0;
+
    'ana': 35,
−
 
+
    'luisa': 22,
−
   return alumnos[nombre]!;
+
   };
 +
 
 +
   return alumnos[nombre] ?? 0;
 
}
 
}
  
−
String obtenerPremio(int edad){
+
String obtenerPremio(int edad) {
−
 
+
 
−
  // ACCEDEMOS A INTERNET A BUSCAR LOS PREMIOS
+
   final premios = ['Jamón', 'Coche', 'Piso'];
−
   final premios = ['Jamón', 'Coche','Piso'];
+
 
−
 
+
   return premios[edad % premios.length];
−
   return premios[edad%3];
 
−
 
 
 
}
 
}
  
 
void main() {
 
void main() {
−
 
 
−
  int edad = obtenerEdad('pedro');
 
−
  String premio = obtenerPremio(edad);
 
−
 
 
−
  print('Pedro con la edad de $edad, tiene de premio:$premio');
 
−
 
 
  
 +
  final edad = obtenerEdad('pedro');
 +
 +
  final premio = obtenerPremio(edad);
 +
 +
  print(
 +
    'Pedro con la edad de $edad tiene de premio: $premio',
 +
  );
 
}
 
}
−
</syntaxhighlight>  
+
</syntaxhighlight>
  
  
 +
<br />
  
−
<br />
 
 
'''APARTADO B)'''
 
'''APARTADO B)'''
−
:<syntaxhighlight lang="java" enclose="div" highlight="" >
 
−
Future<int> obtenerEdad(String nombre){
 
−
 
 
−
  // ACCEDEMOS A INTERNET A BUSCAR LA EDAD
 
−
  final alumnos = {'pedro' : 27, 'ana' : 35, 'luisa' : 22};
 
−
 
 
−
  if (!alumnos.containsKey(nombre)) return Future.value(0);
 
−
 
 
−
  return Future.delayed(const Duration(seconds: 4),() { print ('Obtemmos la edad...');
 
−
                                                        return alumnos[nombre]!;});
 
  
 +
:<syntaxhighlight lang="dart">
 +
Future<int> obtenerEdad(String nombre) async {
 +
 +
  final alumnos = {
 +
    'pedro': 27,
 +
    'ana': 35,
 +
    'luisa': 22,
 +
  };
 +
 +
  await Future.pause(
 +
    const Duration(seconds: 4),
 +
  );
 +
 +
  print('Obtenemos la edad...');
 +
 +
  return alumnos[nombre] ?? 0;
 
}
 
}
  
−
Future<String> obtenerPremio(int edad){
+
Future<String> obtenerPremio(int edad) async {
−
 
+
 
−
  // ACCEDEMOS A INTERNET A BUSCAR LOS PREMIOS
+
   final premios = ['Jamón', 'Coche', 'Piso'];
−
   final premios = ['Jamón', 'Coche','Piso'];
 
−
 
 
−
  return Future.delayed(const Duration(seconds: 2),(){ print('Obtemos el premio...');
 
−
                                                      return premios[edad%3]; });
 
  
 +
  await Future.pause(
 +
    const Duration(seconds: 2),
 +
  );
 +
 +
  print('Obtenemos el premio...');
 +
 +
  return premios[edad % premios.length];
 
}
 
}
  
−
void main() async {
+
Future<void> main() async {
−
 
+
 
−
   int edad = await obtenerEdad('pedro');
+
   final edad = await obtenerEdad('pedro');
−
   String premio = await obtenerPremio(edad);
+
 
−
 
+
   final premio = await obtenerPremio(edad);
−
   print('Pedro con la edad de $edad, tiene de premio:$premio');
+
 
−
 
+
   print(
 +
    'Pedro con la edad de $edad tiene de premio: $premio',
 +
  );
 
}
 
}
−
</syntaxhighlight>  
+
</syntaxhighlight>
  
  
 +
<br />
  
 +
===Resumen===
  
 +
* <code>Future&lt;T&gt;</code> representa un valor de tipo <code>T</code> que estará disponible posteriormente.
  
−
<br />
+
* Un Future termina:
 +
:* con un valor;
 +
:* o con un error.
 +
 
 +
* Los Future '''no implican automáticamente la creación de nuevos hilos'''.
 +
 
 +
* La asincronía habitual en Dart funciona mediante el '''event loop''' de cada isolate.
 +
 
 +
* <code>then()</code> permite registrar una función que se ejecutará cuando el Future se complete correctamente.
 +
 
 +
* <code>catchError()</code> permite gestionar errores de una cadena de Futures.
 +
 
 +
* <code>whenComplete()</code> ejecuta una acción independientemente de que la operación termine correctamente o con error.
 +
 
 +
* <code>async</code> indica que una función es asíncrona y devuelve un Future.
 +
 
 +
* <code>await</code> suspende temporalmente una función <code>async</code> hasta que se obtiene el resultado de un Future, '''pero no bloquea todo el programa'''.
 +
 
 +
* Para la mayoría de código nuevo se recomienda utilizar <code>async</code>, <code>await</code> y <code>try-catch</code> por resultar más sencillo de leer.
 +
 
 +
* Para realizar cálculos costosos realmente en paralelo se pueden utilizar '''isolates'''.
  
−
[https://wiki.cifprodolfoucha.es/index.php?title=Programaci%C3%B3n_de_dispositivos_m%C3%B3viles_con_FLUTTER#Unidad_2:_Lenguaje_DART Enlace a la página principal de la UD2]
+
* <code>Future.pause()</code> puede utilizarse en Dart actual cuando únicamente queremos esperar un intervalo de tiempo.
  
−
[https://wiki.cifprodolfoucha.es/index.php?title=Programaci%C3%B3n_de_dispositivos_m%C3%B3viles_con_FLUTTER Enlace a la página principal del curso]
 
  
  
  
  
 +
<br />
 +
[https://wiki.cifprodolfoucha.es/index.php?title=Programaci%C3%B3n_de_dispositivos_m%C3%B3viles_con_FLUTTER#Unidad_2:_Lenguaje_DART Enlace a la página principal de la UD2]
  
−
<br> -- [[Usuario:angelfg|Ángel D. Fernández González]] -- (2021).
+
[https://wiki.cifprodolfoucha.es/index.php?title=Programaci%C3%B3n_de_dispositivos_m%C3%B3viles_con_FLUTTER Enlace a la página principal del curso]

Revisión actual del 10:53 4 oct 2026

Introducción

  • Más información:
  • La programación asíncrona permite iniciar operaciones que pueden tardar un tiempo en completarse, como una petición HTTP, la lectura de un fichero o el acceso a una base de datos, sin bloquear la ejecución del programa mientras se espera por el resultado.
  • En Dart, la programación asíncrona se basa principalmente en:
  • Future: representa un resultado que estará disponible en el futuro.
  • async / await: permiten escribir código asíncrono con una estructura similar al código síncrono.
  • Stream: permite recibir una secuencia de valores de forma asíncrona.



  • Es importante distinguir asincronía de paralelismo.
Un Future no implica que el código se ejecute automáticamente en un hilo diferente.
Normalmente el código Dart se ejecuta dentro de un isolate que dispone de su propio event loop y su propia zona de memoria. Mientras una operación asíncrona está esperando por una operación externa (como un Future), el event loop puede continuar procesando otros eventos. Cada isolate emplea su propio hilo de ejecución.
Si necesitamos ejecutar cálculos intensivos realmente en paralelo, Dart permite utilizar otros isolates. Para tareas puntuales puede emplearse, por ejemplo, Isolate.run().


Futures

  • Más información:
  • Un objeto Future<T> representa el resultado de una operación asíncrona que podrá completarse posteriormente:
  • con un valor de tipo T, si la operación finaliza correctamente (por ejemplo, llamamos a una lectura en disco de un archivo y va a devolver una cadena en un futuro);
  • con un error, si la operación falla.
  • Cuando una función inicia una operación asíncrona normalmente devuelve inmediatamente un Future. El programa puede continuar ejecutando otras tareas mientras espera a que ese Future se complete.


void main() {

  print('Inicia programa');

  getImagenes('imagenes/animales').then(
    (datos) => print(datos),
  );

  print('Fin programa');
}

Future<List<String>> getImagenes(String path) {

  final datos = ['1.jpg', '2.jpg', '3.jpg', '4.jpg'];

  return Future.delayed(
    const Duration(seconds: 4),
    () => datos,
  );

  // También podríamos utilizar:
  // return Future.value(datos);
}
  • Future.delayed() no detiene ni bloquea el hilo durante cuatro segundos.
Lo que hace es crear un Future cuya operación se ejecutará después de que transcurra, como mínimo, el tiempo indicado. Mientras tanto, el event loop puede continuar procesando otras tareas.
  • En versiones actuales de Dart también existe:
await Future.pause(const Duration(seconds: 4));
Future.pause() resulta apropiado cuando únicamente queremos esperar un determinado intervalo de tiempo y no necesitamos ejecutar ningún callback asociado al temporizador.



Gestión de errores

  • Cuando trabajamos directamente con la API de Future, disponemos principalmente de dos formas de tratar los errores:
  • mediante el parámetro onError de then();
  • mediante el método catchError().




Empleando onError

  • El método then() permite definir:
  • una función que se ejecutará si el Future termina correctamente;
  • opcionalmente, una función onError que se ejecutará si el Future termina con un error.
void main() {

  getImages('imagenes/animales').then(
    (datos) {
      datos.forEach((elem) => print('Dato: $elem'));
    },
    onError: (error) {
      print('Error: $error');
    },
  );
}

Future<List<String>> getImages(String path) {

  return Future.delayed(
    const Duration(seconds: 3),
    () => throw Exception('Error cargando imágenes'),
  );
}
  • En este caso:
  • si getImages() termina correctamente, se ejecuta el primer callback de then();
  • si getImages() termina con un error, se ejecuta onError.

Importante: onError gestiona el error con el que llega el Future a then().

Si se produce un nuevo error dentro del callback de éxito de then(), ese error pertenece al Future devuelto por then() y no será gestionado por ese mismo onError.

Por ejemplo:

miFuture().then(
  (valor) {
    throw Exception('Error producido dentro del then');
  },
  onError: (error) {
    print('Error del Future original: $error');
  },
);

En este caso, onError no gestiona la excepción producida dentro del callback de éxito.



Empleando catchError

  • Otra posibilidad consiste en añadir catchError() después de then():
void main() {

  getImages('imagenes/animales')
      .then((datos) {
        datos.forEach((elem) => print('Dato: $elem'));
      })
      .catchError((error) {
        print('Error: $error');
      });
}
  • catchError() permite capturar:
  • el error con el que se haya completado el Future original;
  • errores producidos dentro de los callbacks then() anteriores de la cadena.

Por ejemplo:

miFuture()
    .then((valor) {
      print(valor);

      throw Exception('Error dentro del then');
    })
    .catchError((error) {
      print('Error capturado: $error');
    });

En este caso, catchError() también captura la excepción producida dentro de then().



Diferencia entre onError y catchError

Podemos resumir su comportamiento de esta forma:

Mecanismo Error del Future original Error producido dentro de then()
then(..., onError: ...) Sí No, si se produce dentro del callback de éxito de ese mismo then()
then(...).catchError(...) Sí Sí

Por ello, generalmente resulta más sencillo utilizar:

future
    .then(procesarResultado)
    .catchError(procesarError);

que:

future.then(
  procesarResultado,
  onError: procesarError,
);

No obstante, onError sigue siendo útil cuando necesitamos tratar de forma diferente el error procedente del Future original y los posibles errores producidos posteriormente en la cadena.



Forma recomendada con async / await

  • Cuando utilizamos async y await, normalmente resulta más sencillo emplear try-catch:
Future<void> main() async {

  try {

    final datos = await getImages('imagenes/animales');

    print(datos);

  } catch (error) {

    print('Error: $error');
  }
}
  • Actualmente esta forma suele ser la más sencilla y legible para la mayor parte del código asíncrono.

Estado de un Future

  • Un Future puede encontrarse en dos estados:
  • Incompleto (uncompleted): todavía no dispone de un resultado.
  • Completo (completed): la operación ha terminado.
  • Cuando un Future se completa puede hacerlo:
  • con un valor;
  • con un error.
  • El callback principal de then() se ejecuta cuando el Future se completa correctamente con un valor.
  • El método whenComplete() se ejecuta independientemente de que el Future haya terminado con un valor o con un error.
Su comportamiento es similar al bloque finally de un try-catch-finally.
  • Más información:
void main() {

  print('Inicia programa');

  cargarImagenes()
      .then((_) => print('Acabó el Future1 sin errores'))
      .whenComplete(() => print('Terminó Future1'));

  cargarImagenesConError()
      .then((_) => print('Acabó el Future2 sin errores'))
      .catchError((error) {
        print('Capturando el error: $error');
      })
      .whenComplete(() => print('Terminó Future2'));

  print('Fin programa');
}

Future<void> cargarImagenesConError() {

  return Future.delayed(
    const Duration(seconds: 2),
    () => throw Exception('Error cargando imágenes'),
  );
}

Future<void> cargarImagenes() {

  return Future.delayed(
    const Duration(seconds: 2),
    () => print('Cargamos imágenes'),
  );
}
  • cargarImagenes() se completa correctamente. Por lo tanto se ejecutan then() y posteriormente whenComplete().
  • cargarImagenesConError() se completa con un error. Por lo tanto no se ejecuta el callback de éxito de then(), el error es tratado por catchError() y finalmente se ejecuta whenComplete().



Async/Await

  • Más información:
  • En los ejemplos anteriores hemos utilizado then() para registrar una función que se ejecutará cuando un Future se complete correctamente.
  • Sin embargo, Dart proporciona las palabras clave async y await, que permiten escribir código asíncrono con una estructura mucho más similar a la del código síncrono.
  • Es importante recordar que los Future no se ejecutan necesariamente en otro hilo.
Dentro de un isolate, Dart utiliza un event loop para procesar los eventos y operaciones asíncronas.
  • Cuando utilizamos await, no bloqueamos el hilo ni el isolate.
Lo que se suspende temporalmente es la ejecución de la función async actual. Mientras se espera a que el Future se complete, el event loop puede continuar procesando otras tareas.



Uso de await

Para obtener el resultado de un Future podemos utilizar await:

Future<void> main() async {

  print('Inicia programa');

  final datos = await getImages('imagenes/animales');

  print('Datos obtenidos después de ejecutar el Future: $datos');

  print('Fin programa');
}

Future<List<String>> getImages(String path) {

  final imagenes = ['1.jpg', '2.jpg', '3.jpg'];

  // Búsqueda de imágenes a partir del path.

  return Future.delayed(
    const Duration(seconds: 3),
    () => imagenes,
  );
}
  • Para utilizar await:
  • la llamada debe encontrarse dentro de una función marcada como async;
  • normalmente la expresión que sigue a await devuelve un Future.
  • Si tenemos:
Future<List<String>> future = getImages('imagenes/animales');
la variable contiene el propio objeto Future<List<String>>.
  • En cambio:
List<String> datos = await getImages('imagenes/animales');
await suspende la función actual hasta que el Future se completa y la expresión produce el valor contenido en el Future.

Nota importante: await no hace que el Future deje de ser asíncrono ni bloquea el programa completo. Solo suspende temporalmente la función async en la que aparece.



Funciones async

  • Una función marcada como async devuelve un Future.

Si queremos devolver un String:

Future<String> obtenerDato() async {

  return 'Dato obtenido';
}

Aunque dentro de la función devolvamos un String, la llamada devuelve:

Future<String>

Dart se encarga automáticamente de completar el Future con el valor retornado.

Por ello no es necesario escribir:

return Future.value(resultado);

y podemos utilizar directamente:

return resultado;



Ejemplo con lectura de un fichero

Future<String> getDatoLocal(String path) async {

  final file = File(path);

  final resultado = await file.readAsString();

  return resultado;
}
  • readAsString() devuelve un Future<String>.
  • Como queremos trabajar con el contenido una vez finalizada la lectura utilizamos await.
  • Al estar utilizando await, la función debe estar marcada como async.
  • Aunque devolvemos directamente resultado, el tipo de retorno de la función es Future<String>.

Si no necesitásemos realizar ninguna operación con el resultado antes de devolverlo, incluso podríamos simplificar la función:

Future<String> getDatoLocal(String path) {

  return File(path).readAsString();
}



Gestión de errores con async / await

  • Actualmente, para la mayoría de los casos, la forma más sencilla de gestionar errores asíncronos es utilizar await junto con try-catch.
Future<void> main() async {

  try {

    final datos = await getImages('imagenes/animales');

    print(datos);

  } catch (error, stackTrace) {

    print('Error: $error');
    print(stackTrace);
  }
}
  • Si el Future esperado mediante await se completa con un error, dicho error se comportará en ese punto como una excepción y podrá ser capturado mediante try-catch.
  • También podemos utilizar finally:
try {

  final datos = await getImages('imagenes/animales');
  print(datos);

} catch (error) {

  print('Error: $error');

} finally {

  print('Operación finalizada');
}
Este patrón equivale conceptualmente a utilizar then(), catchError() y whenComplete(), pero suele resultar más sencillo de leer.



Ejercicios propuestos

Ejercicio 1:

Sabrías aplicar los conceptos aprendidos para que el pedido aparezca correctamente:

String createOrderMessage() {

  var order = fetchUserOrder();

  return 'Tu pedido es: $order';
}

Future<String> fetchUserOrder() =>
    Future.delayed(
      const Duration(seconds: 2),
      () => 'Café largo',
    );

void main() {

  print('Preparando el pedido...');

  print(createOrderMessage());

  print('Pedido finalizado...');
}

Modifica el programa utilizando async y await para que primero se espere a obtener el pedido y posteriormente se muestre el mensaje.



Ejercicio 2:

Apartado A)

  • Se necesita mostrar cuál es el premio que recibe un alumno en función de la edad que tenga.
  • Crea un método obtenerEdad al que se le envíe un nombre y que busque en un mapa la edad correspondiente a dicho nombre.
  • Si el nombre no existe en el mapa debe devolver el valor 0.
  • Crea un método obtenerPremio al que se le envíe una edad y que devuelva, basándose en una lista, el premio correspondiente.
  • Utiliza el operador resto (%) para obtener siempre una posición válida dentro de la lista de premios.
  • Desde la función main, llama a obtenerEdad y utiliza el resultado para llamar posteriormente a obtenerPremio.


Apartado B)

  • Ahora supón que ambos métodos realizan operaciones asíncronas, por ejemplo accesos a un servidor a través de Internet.
  • Modifica las funciones para que devuelvan un Future y utiliza async y await para mantener el orden correcto de ejecución.
  • Simula que obtenerEdad tarda 4 segundos en obtener el resultado y que obtenerPremio tarda 2 segundos.

Importante: Estas operaciones son asíncronas, pero eso no significa que Dart cree automáticamente un hilo diferente para cada una.



Solución Ejercicio 2

APARTADO A)

int obtenerEdad(String nombre) {

  final alumnos = {
    'pedro': 27,
    'ana': 35,
    'luisa': 22,
  };

  return alumnos[nombre] ?? 0;
}

String obtenerPremio(int edad) {

  final premios = ['Jamón', 'Coche', 'Piso'];

  return premios[edad % premios.length];
}

void main() {

  final edad = obtenerEdad('pedro');

  final premio = obtenerPremio(edad);

  print(
    'Pedro con la edad de $edad tiene de premio: $premio',
  );
}



APARTADO B)

Future<int> obtenerEdad(String nombre) async {

  final alumnos = {
    'pedro': 27,
    'ana': 35,
    'luisa': 22,
  };

  await Future.pause(
    const Duration(seconds: 4),
  );

  print('Obtenemos la edad...');

  return alumnos[nombre] ?? 0;
}

Future<String> obtenerPremio(int edad) async {

  final premios = ['Jamón', 'Coche', 'Piso'];

  await Future.pause(
    const Duration(seconds: 2),
  );

  print('Obtenemos el premio...');

  return premios[edad % premios.length];
}

Future<void> main() async {

  final edad = await obtenerEdad('pedro');

  final premio = await obtenerPremio(edad);

  print(
    'Pedro con la edad de $edad tiene de premio: $premio',
  );
}



Resumen

  • Future<T> representa un valor de tipo T que estará disponible posteriormente.
  • Un Future termina:
  • con un valor;
  • o con un error.
  • Los Future no implican automáticamente la creación de nuevos hilos.
  • La asincronía habitual en Dart funciona mediante el event loop de cada isolate.
  • then() permite registrar una función que se ejecutará cuando el Future se complete correctamente.
  • catchError() permite gestionar errores de una cadena de Futures.
  • whenComplete() ejecuta una acción independientemente de que la operación termine correctamente o con error.
  • async indica que una función es asíncrona y devuelve un Future.
  • await suspende temporalmente una función async hasta que se obtiene el resultado de un Future, pero no bloquea todo el programa.
  • Para la mayoría de código nuevo se recomienda utilizar async, await y try-catch por resultar más sencillo de leer.
  • Para realizar cálculos costosos realmente en paralelo se pueden utilizar isolates.
  • Future.pause() puede utilizarse en Dart actual cuando únicamente queremos esperar un intervalo de tiempo.




Enlace a la página principal de la UD2

Enlace a la página principal del curso