Programación asincrónica: futures, async, await
Este codelab te enseña cómo escribir código asincrónico usando futures y las palabras clave async
y await
. Con los editores DartPad integrados, puedes poner a prueba tus conocimientos ejecutando código de ejemplo y completando ejercicios.
Para aprovechar al máximo este codelab, debes tener lo siguiente:
- Conocimiento de sintaxis básica de Dart.
- Algo de experiencia escribiendo código asincrónico en otro lenguaje.
Este codelab cubre el siguiente material:
- Cómo y cuándo usar las palabras clave
async
yawait
. - Cómo el uso de
async
yawait
afecta el orden de ejecución. - Cómo manejar errores de una llamada asincrónica usando expresiones
try-catch
en funcionesasync
.
Tiempo estimado para completar este codelab: 40-60 minutos.
Los ejercicios de este codelab tienen fragmentos de código parcialmente completados. Puedes utilizar DartPad para poner a prueba tus conocimientos completando el código y haciendo clic en el botón Run. No edites el código de tests en la función main
o debajo.
Si necesitas ayuda, expande el menú desplegable Sugerencia o Solución después de cada ejercicio.
Por qué es importante el código asincrónico
Las operaciones asincrónicas permiten que tu programa complete su trabajo mientras espera que finalice otra operación. A continuación se muestran algunas operaciones asincrónicas comunes:
- Obtención de datos a través de la red.
- Escritura en una base de datos.
- Leer datos de un archivo.
Estos cálculos asincrónicos generalmente proporcionan su resultado como un Future
o, si el resultado tiene varias partes, como un Stream
. Estos cálculos introducen asincronía en un programa. Para adaptarse a esa asincronía inicial, otras funciones simples de Dart también deben volverse asincrónicas.
Para interactuar con estos resultados asincrónicos, puedes usar las palabras clave async
y await
. La mayoría de las funciones asincrónicas son simplemente funciones asincrónicas de Dart que dependen, posiblemente en el fondo, de un cálculo inherentemente asincrónico.
Ejemplo: Usar incorrectamente una función asíncrona
El siguiente ejemplo muestra la forma incorrecta de usar una función asincrónica (fetchUserOrder()
). Más adelante arreglarás el ejemplo usando async
y await
. Antes de ejecutar este ejemplo, intenta detectar el problema: ¿cuál crees que será el resultado?
He aquí por qué el ejemplo no imprime el valor que finalmente produce fetchUserOrder()
:
fetchUserOrder()
es una función asincrónica que, después de un delay, proporciona una cadena que describe el pedido del usuario: un “Large Latte”.- Para obtener el pedido del usuario,
createOrderMessage()
debe llamar afetchUserOrder()
y esperar a que finalice. Debido a quecreateOrderMessage()
no espera a quefetchUserOrder()
termine,createOrderMessage()
no logra obtener el valor de cadena quefetchUserOrder()
finalmente proporciona. - En cambio,
createOrderMessage()
obtiene una representación del trabajo pendiente por realizar: un Future incompleto. Aprenderás más sobre futuros en la siguiente sección. - Debido a que
createOrderMessage()
no puede obtener el valor que describe el pedido del usuario, el ejemplo no puede imprimir “Large Latte” en la consola y en su lugar imprime “Tu pedido es: Instancia de ‘_Future<String>’”.
En las siguientes secciones aprenderás sobre futuros y cómo trabajar con futuros (usando async
y await
) para que puedas escribir el código necesario para hacer que fetchUserOrder()
imprima el valor deseado (“Large Latte”) en la consola.
¿Qué es un Future
?
Un future
(“f” en minúscula) es una instancia de la clase Future ↗ (“F” mayúscula). Un futuro representa el resultado de una operación asincrónica y puede tener dos estados: incompleto o completado.
Incompleto
Cuando llamas a una función asincrónica, devuelve un futuro incompleto. Ese futuro está esperando a que finalice la operación asincrónica de la función o arroje un error.
Completado
Si la operación asincrónica tiene éxito, el futuro se completa con un valor. De lo contrario, se completa con un error.
Completado con un valor
Un futuro de tipo Future<T>
se completa con un valor de tipo T
. Por ejemplo, un futuro con tipo Future<String>
produce un valor de tipo String
. Si un futuro no produce un valor utilizable, entonces el tipo de futuro es Future<void>
.
Completado con un error
Si la operación asincrónica que realiza la función falla por algún motivo, el futuro se completa con un error.
Ejemplo: Introducción a futuros
En el siguiente ejemplo, fetchUserOrder()
devuelve un futuro que se completa después de imprimir en la consola. Debido a que no devuelve un valor utilizable, fetchUserOrder()
tiene el tipo Future<void>
. Antes de ejecutar el ejemplo, intenta predecir cuál se imprimirá primero: “Latte grande” o “Obteniendo pedido del usuario…”.
En el ejemplo anterior, aunque fetchUserOrder()
se ejecuta antes de la llamada a print()
en la línea 8, la consola muestra el resultado de la línea 8 (“Obteniendo orden de usuario… ”) antes de la salida de fetchUserOrder()
(“Large Latte”). Esto se debe a que fetchUserOrder()
demora (delayed
) antes de imprimir “Large Latte”.
Ejemplo: Completando con un error
Ejecuta el siguiente ejemplo para ver cómo un futuro se completa con un error. Un poco más adelante aprenderás cómo manejar el error.
En este ejemplo, fetchUserOrder()
se completa con un error que indica que el ID de usuario no es válido.
Has aprendido sobre los futuros y cómo se completan, pero ¿cómo usas los resultados de las funciones asincrónicas? En la siguiente sección aprenderás cómo obtener resultados con las palabras clave async
y await
.
Trabajando con futuros: async
and await
Las palabras clave async
y await
proporcionan una forma declarativa de definir funciones asincrónicas y usar sus resultados. Recuerda estas dos pautas básicas cuando utilices async
y await
:
- Para definir una función asíncrona, agrega
async
antes del cuerpo de la función: - La palabra clave
await
funciona solo en funcionesasync
.
Aquí hay un ejemplo que convierte main()
de una función sincrónica a asincrónica.
Primero, agrega la palabra clave async
antes del cuerpo de la función:
Si la función tiene un tipo de retorno declarado, entonces actualiza el tipo para que sea Future<T>
, donde T
es el tipo del valor que devuelve la función. Si la función no devuelve explícitamente un valor, entonces el tipo de retorno es Future<void>
:
Ahora que tienes una función async
, puedes usar la palabra clave await
para esperar a que se complete un futuro:
Como muestran los dos ejemplos siguientes, las palabras clave async
y await
dan como resultado un código asincrónico que se parece mucho al código sincrónico. Las únicas diferencias se resaltan en el ejemplo asincrónico, que (si tu ventana es lo suficientemente amplia) está a la derecha del ejemplo sincrónico.
Ejemplo: funciones síncronas
Ejemplo: funciones asincrónicas
El ejemplo asincrónico se diferencia en tres formas:
- El tipo de retorno para
createOrderMessage()
cambia deString
aFuture<String>
. - La palabra clave
async
aparece antes de los cuerpos de las funciones paracreateOrderMessage()
ymain()
. - La palabra clave
await
aparece antes de llamar a las funciones asincrónicasfetchUserOrder()
ycreateOrderMessage()
.
Flujo de ejecución con async y await
Una función async
se ejecuta sincrónicamente hasta la primera palabra clave await
. Esto significa que dentro del cuerpo de una función async
, todo el código síncrono antes de la primera palabra clave await
se ejecuta inmediatamente.
Ejemplo: Ejecución dentro de funciones asíncronas
Ejecuta el siguiente ejemplo para ver cómo procede la ejecución dentro del cuerpo de una función async
. ¿Cuál crees que será el resultado?
Después de ejecutar el código del ejemplo anterior, intenta invertir las líneas 2 y 3:
Observa que el tiempo de la salida cambia, ahora que aparece print('Esperando pedido del usuario...')
después de la primera palabra clave await
en printOrderMessage()
.
Ejercicio: Practica usando async
y await
El siguiente ejercicio es un test unitario fallido que contiene fragmentos de código parcialmente completados. Tu tarea es completar el ejercicio escribiendo código para aprobar los tests. No es necesario implementar main()
.
Para simular operaciones asincrónicas, llama a las siguientes funciones, que se te proporcionan:
Función | Firma de tipo | Descripción |
---|---|---|
fetchRole() | Future<String> fetchRole() | Obtiene una breve descripción del rol del usuario. |
fetchLoginAmount() | Future<int> fetchLoginAmount() | Obtiene el número de veces que un usuario ha iniciado sesión. |
Parte 1: reportUserRole()
Agrega código a la función reportUserRole()
para que haga lo siguiente:
- Devuelve un futuro que se completa con la siguiente cadena:
"Rol de usuario: <user role>"
- Nota: Debes usar el valor real devuelto por
fetchRole()
; copiar y pegar el valor de retorno del ejemplo no hará que el test pase. - Valor de retorno de ejemplo:
"Rol de usuario: tester"
- Nota: Debes usar el valor real devuelto por
- Obtiene el rol de usuario llamando a la función proporcionada
fetchRole()
.
Parte 2: reportLogins()
Implementa una función async
llamada reportLogins()
para que haga lo siguiente:
- Devuelve la cadena
"Número total de inicios de sesión: <# of logins>"
.- Nota: Debes usar el valor real devuelto por
fetchLoginAmount()
; copiar y pegar el valor de retorno del ejemplo no hará que el test pase. - Valor de retorno de ejemplo de
reportLogins()
:"Número total de inicios de sesión: 57"
- Nota: Debes usar el valor real devuelto por
- Obtiene el número de inicios de sesión llamando a la función proporcionada
fetchLoginAmount()
.
Manejo de errores
Para manejar errores en una función async
, usa try-catch
:
Dentro de una función async
, puedes escribir cláusulas try-catch
↗ de la misma manera que lo harías en código sincrónico.
Ejemplo: async
y await
con try-catch
Ejecuta el siguiente ejemplo para ver cómo manejar un error de una función asincrónica. ¿Cuál crees que será el resultado?
Ejercicio: Practica el manejo de errores
El siguiente ejercicio proporciona práctica para manejar errores con código asincrónico, utilizando el enfoque descrito en la sección anterior. Para simular operaciones asincrónicas, tu código llamará a la siguiente función, que se te proporciona:
Función | Firma de tipo | Descripción |
---|---|---|
fetchNewUsername() | Future<String> fetchNewUsername() | Devuelve el nuevo nombre de usuario que puedes usar para reemplazar uno antiguo. |
Usa async
y await
para implementar una función asincrónica changeUsername()
que hace lo siguiente:
- Llama a la función asincrónica proporcionada
fetchNewUsername()
y devuelve su resultado.- Ejemplo de valor de retorno de
changeUsername()
:"jane_smith_92"
- Ejemplo de valor de retorno de
- Detecta cualquier error que ocurra y devuelve el valor de cadena del error.
- Puedes usar el método toString() ↗ para encadenar tanto Excepciones ↗ como Errores. ↗
Ejercicio: Poniéndolo todo junto
Es hora de practicar lo aprendido en un ejercicio final. Para simular operaciones asincrónicas, este ejercicio proporciona las funciones asincrónicas fetchUsername()
y logoutUser()
:
Función | Firma de tipo | Descripción |
---|---|---|
fetchUsername() | Future<String> fetchUsername() | Devuelve el nombre asociado con el usuario actual. |
logoutUser() | Future<String> logoutUser() | Realiza el cierre de sesión del usuario actual y devuelve el nombre de usuario con el que se cerró la sesión. |
Escribe lo siguiente:
Parte 1: addHello()
- Escribe una función
addHello()
que tome un solo argumentoString
. addHello()
devuelve su argumentoString
precedido por'Hola '
. Ejemplo:addHello('Jon')
devuelve'Hola Jon'
.
Parte 2: greetUser()
- Escribe una función
greetUser()
que no acepte argumentos. - Para obtener el nombre de usuario,
greetUser()
llama a la función asincrónica proporcionadafetchUsername()
. greetUser()
crea un saludo para el usuario llamando aaddHello()
, pasándole el nombre de usuario y devolviendo el resultado. Ejemplo: sifetchUsername()
devuelve'Jenny'
, entoncesgreetUser()
devuelve'Hola Jenny'
.
Parte 3: sayGoodbye()
- Escribe una función
sayGoodbye()
que haga lo siguiente:- No acepta argumentos.
- Detecta cualquier error.
- Llama a la función asincrónica proporcionada
logoutUser()
.
- Si
logoutUser()
falla,sayGoodbye()
devuelve cualquier cadena que desees. - Si
logoutUser()
tiene éxito,sayGoodbye()
devuelve la cadena'<result> Gracias, nos vemos la próxima vez'
, donde<result>
es el valor de cadena devuelto al llamar alogoutUser()
.
¿Qué sigue?
¡Felicitaciones, has terminado el codelab! Si deseas obtener más información, aquí tienes algunas sugerencias sobre dónde ir a continuación:
- Juega con DartPad ↗.
- Prueba con otro codelab.
- Obtén más información sobre futuros y código asincrónico en Dart:
- Tutorial de streams ↗: aprende a trabajar con una secuencia de eventos asincrónicos.
- Concurrencia en Dart ↗: Comprende y aprende cómo implementar la concurrencia en Dart.
- Soporte de asincronía ↗: Sumérgete en el soporte de biblioteca y lenguaje de Dart para programación asincrónica.
- Vídeos de Dart de Google ↗: mira uno o más vídeos sobre programación asincrónica.
- ¡Obtén el Dart SDK ↗!