Diferencia entre revisiones de «Flutter DART Programación asíncrona»
| Línea 1: | Línea 1: | ||
| + | ```mediawiki | ||
==Introducción== | ==Introducción== | ||
| − | * Más información: https://dart.dev/ | + | * 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. | ||
| + | |||
| + | * 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'''. Mientras una operación asíncrona está esperando por una operación externa, el event loop puede continuar procesando otros eventos. | ||
| + | |||
| + | : 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 /> | ||
| + | |||
==Futures== | ==Futures== | ||
| − | * Más información: | + | * Más información: |
| − | :* https://dart.dev/ | + | :* https://api.dart.dev/dart-async/Future-class.html |
| − | :* https://dart.dev/ | + | :* https://dart.dev/libraries/dart-async |
| − | :* https://dart.dev/ | + | :* https://dart.dev/libraries/async/async-await |
| + | :* https://dart.dev/libraries/async/futures-error-handling | ||
| + | * Un objeto <code>Future<T></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; | ||
| + | :* 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 43: | ||
</center> | </center> | ||
| + | <br /> | ||
| − | + | :<syntaxhighlight lang="dart" enclose="div" highlight="5,13"> | |
| − | :<syntaxhighlight lang=" | ||
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) { |
| − | + | ||
| − | + | final datos = ['1.jpg', '2.jpg', '3.jpg', '4.jpg']; | |
| − | + | ||
| − | + | return Future.delayed( | |
| − | return Future.delayed(Duration(seconds: 4),() => datos); | + | 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=== | ||
| Línea 58: | Línea 92: | ||
</center> | </center> | ||
| − | :<syntaxhighlight lang=" | + | * Los errores producidos por operaciones asíncronas deben tratarse dentro de la cadena de Futures o mediante <code>await</code> y <code>try-catch</code>. |
| + | |||
| + | * Utilizando directamente la API de Future podemos utilizar <code>catchError()</code>: | ||
| + | |||
| + | :<syntaxhighlight lang="dart" enclose="div"> | ||
void main() { | void main() { | ||
print('Inicia programa'); | print('Inicia programa'); | ||
| − | + | ||
| − | + | getImages('imagenes/animales') | |
| − | + | .then((datos) { | |
| − | + | datos.forEach((elem) => print('Dato: $elem')); | |
| − | + | }) | |
| − | + | .catchError((error, stackTrace) { | |
| − | + | print('Error: $error'); | |
| − | + | }); | |
| − | + | ||
| − | |||
print('Fin programa'); | print('Fin programa'); | ||
| − | |||
} | } | ||
| − | Future<List<String>> getImages(String path){ | + | Future<List<String>> getImages(String path) { |
| − | + | ||
| − | + | final imagenes = ['1.jpg', '2.jpg', '3.jpg']; | |
| − | + | ||
| − | + | return Future.delayed( | |
| − | + | const Duration(seconds: 3), | |
| − | + | () => throw Exception('Error cargando las imágenes'), | |
| − | + | ); | |
| − | return Future.delayed(Duration(seconds: 3),() => | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | < | + | * <code>catchError()</code> es el equivalente asíncrono de un bloque <code>catch</code>. |
| + | |||
| + | * Un error producido por el Future anterior en la cadena llegará a <code>catchError()</code>. | ||
| + | |||
| + | * Además, si dentro de un callback de <code>then()</code> se produce una excepción, el Future devuelto por ese <code>then()</code> se completará con dicho error y un <code>catchError()</code> posterior también podrá capturarlo. | ||
| + | |||
| + | Por ejemplo: | ||
| + | |||
| + | :<syntaxhighlight lang="dart"> | ||
| + | miFuture() | ||
| + | .then((valor) { | ||
| + | print(valor); | ||
| + | |||
| + | throw Exception('Error dentro del then'); | ||
| + | }) | ||
| + | .catchError((error) { | ||
| + | print('Error capturado: $error'); | ||
| + | }); | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | '''Nota:''' El parámetro <code>onError</code> de <code>then()</code> trata los errores procedentes del Future sobre el que se invoca <code>then()</code>. Sin embargo, un <code>catchError()</code> colocado después de <code>then()</code> también puede capturar errores generados dentro del callback ejecutado por dicho <code>then()</code>. | ||
| + | |||
| + | |||
| + | <br /> | ||
| + | |||
| + | ====Errores síncronos en funciones que devuelven Future==== | ||
| + | |||
| + | * Hay que diferenciar un error que completa un Future de un error síncrono producido '''antes de que la función llegue a devolver dicho Future'''. | ||
| + | |||
| + | Por ejemplo: | ||
| + | |||
| + | :<syntaxhighlight lang="dart"> | ||
| + | Future<List<String>> getImages(String path) { | ||
| + | final imagenes = ['1.jpg', '2.jpg', '3.jpg']; | ||
| + | // Este error se produce antes de devolver el Future. | ||
| + | throw Exception('Error síncrono'); | ||
| − | + | return Future.value(imagenes); | |
| − | + | } | |
| − | + | </syntaxhighlight> | |
| − | + | En una función de este tipo, la llamada podría lanzar una excepción síncrona antes de que exista un Future al que asociar <code>catchError()</code>. | |
| − | :<syntaxhighlight lang=" | + | |
| + | Una posibilidad para transformar tanto los resultados como los errores síncronos en el resultado de un Future es utilizar <code>Future.sync()</code>: | ||
| + | |||
| + | :<syntaxhighlight lang="dart" enclose="div"> | ||
void main() { | void main() { | ||
print('Inicia programa'); | print('Inicia programa'); | ||
| − | getImages('imagenes/animales').then((datos) { | + | getImages('imagenes/animales') |
| − | + | .then((datos) { | |
| − | + | datos.forEach((elem) => print('Dato: $elem')); | |
| + | }) | ||
| + | .catchError((error) { | ||
| + | print('Hubo un error: $error'); | ||
| + | }); | ||
print('Fin programa'); | print('Fin programa'); | ||
| − | |||
} | } | ||
| − | Future<List<String>> getImages(String path){ | + | Future<List<String>> getImages(String path) { |
return Future.sync(() { | return Future.sync(() { | ||
| − | |||
| − | + | final imagenes = ['1.jpg', '2.jpg', '3.jpg']; | |
| − | + | // Búsqueda de imágenes a partir del path. | |
| − | + | throw Exception('Error'); | |
| + | return imagenes; | ||
}); | }); | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| + | |||
| + | * Si el callback de <code>Future.sync()</code>: | ||
| + | :* devuelve un valor, el Future se completa con dicho valor; | ||
| + | :* devuelve otro Future, se espera al resultado de dicho Future; | ||
| + | :* lanza una excepción, el Future se completa con ese error. | ||
| + | |||
| + | '''Importante:''' En código moderno normalmente resulta más sencillo utilizar funciones <code>async</code> y gestionar los errores mediante <code>try-catch</code>. | ||
<br /> | <br /> | ||
| − | ===Estado | + | ===Estado de un Future=== |
| − | * Un Future puede | + | |
| − | :* Incompleto: | + | * Un Future puede encontrarse en dos estados: |
| − | :* Completo: | + | :* '''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. | ||
| + | * El método <code>whenComplete()</code> se ejecuta independientemente de que el Future haya terminado con un valor o con un error. | ||
| − | * | + | : Su comportamiento es similar al bloque <code>finally</code> de un <code>try-catch-finally</code>. |
| + | |||
| + | * Más información: | ||
| + | :* https://api.dart.dev/dart-async/Future/then.html | ||
| + | :* https://api.dart.dev/dart-async/Future/whenComplete.html | ||
| − | + | :<syntaxhighlight lang="dart" enclose="div"> | |
| − | :<syntaxhighlight lang=" | ||
void main() { | void main() { | ||
print('Inicia programa'); | print('Inicia programa'); | ||
| − | |||
| − | |||
| − | |||
| − | cargarImagenesConError(). | + | 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'); | print('Fin programa'); | ||
| − | |||
} | } | ||
| − | Future<void> cargarImagenesConError(){ | + | |
| − | + | Future<void> cargarImagenesConError() { | |
| − | + | ||
| − | + | return Future.delayed( | |
| + | const Duration(seconds: 2), | ||
| + | () => throw Exception('Error cargando imágenes'), | ||
| + | ); | ||
} | } | ||
| − | Future<void> cargarImagenes(){ | + | Future<void> cargarImagenes() { |
| − | + | ||
| − | + | return Future.delayed( | |
| − | + | const Duration(seconds: 2), | |
| + | () => print('Cargamos imágenes'), | ||
| + | ); | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | :* | + | :* <code>cargarImagenes()</code> se completa correctamente. Por lo tanto se ejecutan <code>then()</code> y posteriormente <code>whenComplete()</code>. |
| − | |||
| + | :* <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/ | + | :* https://dart.dev/language/async |
| − | :* https://api. | + | :* 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'''. | ||
| + | |||
| + | : 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. | ||
| − | |||
| − | |||
| − | + | <br /> | |
| − | :<syntaxhighlight lang=" | + | |
| − | void main() { | + | ===Uso de await=== |
| + | |||
| + | 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'); | ||
| − | |||
} | } | ||
| − | |||
| + | 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<List<String>></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 /> | ||
| + | ===Funciones async=== | ||
| − | * | + | * Una función marcada como <code>async</code> devuelve un Future. |
| − | |||
| − | |||
| − | |||
| + | Si queremos devolver un <code>String</code>: | ||
| − | :<syntaxhighlight lang=" | + | :<syntaxhighlight lang="dart"> |
| − | + | Future<String> obtenerDato() async { | |
| − | + | return 'Dato obtenido'; | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
} | } | ||
| + | </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> | ||
| + | |||
| + | |||
| + | <br /> | ||
| + | |||
| + | ===Ejemplo con lectura de un fichero=== | ||
| + | |||
| + | :<syntaxhighlight lang="dart" enclose="div"> | ||
| + | Future<String> getDatoLocal(String path) async { | ||
| + | |||
| + | final file = File(path); | ||
| − | + | final resultado = await file.readAsString(); | |
| − | + | ||
| − | + | return resultado; | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | return | ||
| − | |||
} | } | ||
| − | </syntaxhighlight> | + | </syntaxhighlight> |
| + | |||
| + | * <code>readAsString()</code> devuelve un <code>Future<String></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<String></code>. | ||
| − | + | 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 /> | ||
| − | |||
| − | |||
| − | :<syntaxhighlight lang=" | + | ===Gestión de errores con async / await=== |
| − | Future< | + | |
| + | * 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>. | ||
| + | |||
| + | :<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 | + | '''Ejercicio 1:''' |
| − | :<syntaxhighlight lang=" | + | |
| + | 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() => | ||
| − | |||
| − | |||
Future.delayed( | Future.delayed( | ||
const Duration(seconds: 2), | const Duration(seconds: 2), | ||
| Línea 271: | Línea 486: | ||
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:''' | ||
| + | |||
| + | '''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 <code>obtenerEdad</code> 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 <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 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=" | + | |
| − | int obtenerEdad(String nombre){ | + | :<syntaxhighlight lang="dart"> |
| − | + | int obtenerEdad(String nombre) { | |
| − | + | ||
| − | final alumnos = {'pedro' : 27, 'ana' : 35, 'luisa' : 22 | + | final alumnos = { |
| − | + | 'pedro': 27, | |
| − | + | 'ana': 35, | |
| − | + | 'luisa': 22, | |
| − | return alumnos[nombre] | + | }; |
| + | |||
| + | return alumnos[nombre] ?? 0; | ||
} | } | ||
| − | String obtenerPremio(int edad){ | + | String obtenerPremio(int edad) { |
| − | + | ||
| − | + | final premios = ['Jamón', 'Coche', 'Piso']; | |
| − | final premios = ['Jamón', 'Coche','Piso']; | + | |
| − | + | return premios[edad % premios.length]; | |
| − | return premios[edad% | ||
| − | |||
} | } | ||
void main() { | void main() { | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| + | final edad = obtenerEdad('pedro'); | ||
| + | |||
| + | final premio = obtenerPremio(edad); | ||
| + | |||
| + | print( | ||
| + | 'Pedro con la edad de $edad tiene de premio: $premio', | ||
| + | ); | ||
} | } | ||
| − | </syntaxhighlight> | + | </syntaxhighlight> |
| + | <br /> | ||
| − | |||
'''APARTADO B)''' | '''APARTADO B)''' | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| + | :<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 { |
| − | + | ||
| − | + | final premios = ['Jamón', 'Coche', 'Piso']; | |
| − | final premios = ['Jamón', 'Coche','Piso']; | + | |
| − | + | await Future.pause( | |
| − | + | const Duration(seconds: 2), | |
| − | + | ); | |
| + | |||
| + | print('Obtenemos el premio...'); | ||
| + | return premios[edad % premios.length]; | ||
} | } | ||
| − | void main() async { | + | Future<void> main() async { |
| − | + | ||
| − | + | final edad = await obtenerEdad('pedro'); | |
| − | + | ||
| − | + | final premio = await obtenerPremio(edad); | |
| − | print('Pedro con la edad de $edad | + | |
| − | + | print( | |
| + | 'Pedro con la edad de $edad tiene de premio: $premio', | ||
| + | ); | ||
} | } | ||
| − | </syntaxhighlight> | + | </syntaxhighlight> |
| + | |||
| + | |||
| + | <br /> | ||
| + | ===Resumen=== | ||
| + | * <code>Future<T></code> representa un valor de tipo <code>T</code> 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. |
| + | |||
| + | * <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'''. | |
| + | * <code>Future.pause()</code> puede utilizarse en Dart actual cuando únicamente queremos esperar un intervalo de tiempo. | ||
| + | <br /> | ||
| + | [https://wiki.cifprodolfoucha.es/index.php?title=Programaci%C3%B3n_de_dispositivos_m%C3%B3biles_con_FLUTTER#Unidad_2:_Lenguaje_DART Enlace a la página principal de la UD2] | ||
| − | + | [https://wiki.cifprodolfoucha.es/index.php?title=Programaci%C3%B3n_de_dispositivos_m%C3%B3biles_con_FLUTTER Enlace a la página principal del curso] | |
| + | ``` | ||
Revisión del 19:03 3 oct 2026
```mediawiki
Sumario
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. Mientras una operación asíncrona está esperando por una operación externa, el event loop puede continuar procesando otros eventos.
- 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; - con un error, si la operación falla.
- con un valor de tipo
- 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
- Los errores producidos por operaciones asíncronas deben tratarse dentro de la cadena de Futures o mediante
awaitytry-catch.
- Utilizando directamente la API de Future podemos utilizar
catchError():
void main() { print('Inicia programa'); getImages('imagenes/animales') .then((datos) { datos.forEach((elem) => print('Dato: $elem')); }) .catchError((error, stackTrace) { print('Error: $error'); }); print('Fin programa'); } Future<List<String>> getImages(String path) { final imagenes = ['1.jpg', '2.jpg', '3.jpg']; return Future.delayed( const Duration(seconds: 3), () => throw Exception('Error cargando las imágenes'), ); }
catchError()es el equivalente asíncrono de un bloquecatch.
- Un error producido por el Future anterior en la cadena llegará a
catchError().
- Además, si dentro de un callback de
then()se produce una excepción, el Future devuelto por esethen()se completará con dicho error y uncatchError()posterior también podrá capturarlo.
Por ejemplo:
miFuture() .then((valor) { print(valor); throw Exception('Error dentro del then'); }) .catchError((error) { print('Error capturado: $error'); });
Nota: El parámetro onError de then() trata los errores procedentes del Future sobre el que se invoca then(). Sin embargo, un catchError() colocado después de then() también puede capturar errores generados dentro del callback ejecutado por dicho then().
Errores síncronos en funciones que devuelven Future
- Hay que diferenciar un error que completa un Future de un error síncrono producido antes de que la función llegue a devolver dicho Future.
Por ejemplo:
Future<List<String>> getImages(String path) { final imagenes = ['1.jpg', '2.jpg', '3.jpg']; // Este error se produce antes de devolver el Future. throw Exception('Error síncrono'); return Future.value(imagenes); }
En una función de este tipo, la llamada podría lanzar una excepción síncrona antes de que exista un Future al que asociar catchError().
Una posibilidad para transformar tanto los resultados como los errores síncronos en el resultado de un Future es utilizar Future.sync():
void main() { print('Inicia programa'); getImages('imagenes/animales') .then((datos) { datos.forEach((elem) => print('Dato: $elem')); }) .catchError((error) { print('Hubo un error: $error'); }); print('Fin programa'); } Future<List<String>> getImages(String path) { return Future.sync(() { final imagenes = ['1.jpg', '2.jpg', '3.jpg']; // Búsqueda de imágenes a partir del path. throw Exception('Error'); return imagenes; }); }
- Si el callback de
Future.sync():
- devuelve un valor, el Future se completa con dicho valor;
- devuelve otro Future, se espera al resultado de dicho Future;
- lanza una excepción, el Future se completa con ese error.
Importante: En código moderno normalmente resulta más sencillo utilizar funciones async y gestionar los errores mediante try-catch.
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
finallyde untry-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 ejecutanthen()y posteriormentewhenComplete().
cargarImagenesConError()se completa con un error. Por lo tanto no se ejecuta el callback de éxito dethen(), el error es tratado porcatchError()y finalmente se ejecutawhenComplete().
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
asyncyawait, 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
asyncactual. 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
awaitdevuelve un Future.
- la llamada debe encontrarse dentro de una función marcada como
- 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');
awaitsuspende 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
asyncdevuelve 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 unFuture<String>.
- Como queremos trabajar con el contenido una vez finalizada la lectura utilizamos
await.
- Al estar utilizando
await, la función debe estar marcada comoasync.
- Aunque devolvemos directamente
resultado, el tipo de retorno de la función esFuture<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
awaitjunto contry-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
awaitse completa con un error, dicho error se comportará en ese punto como una excepción y podrá ser capturado mediantetry-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()ywhenComplete(), 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
obtenerEdadal 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
obtenerPremioal 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 aobtenerEdady utiliza el resultado para llamar posteriormente aobtenerPremio.
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
asyncyawaitpara mantener el orden correcto de ejecución.
- Simula que
obtenerEdadtarda 4 segundos en obtener el resultado y queobtenerPremiotarda 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 tipoTque 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.
asyncindica que una función es asíncrona y devuelve un Future.
awaitsuspende temporalmente una funciónasynchasta 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,awaitytry-catchpor 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.