Futuros de Dart — Flutter en el Foco

Cesar Vega
25 junio, 2019

. . .

Este artículo se basa en el video publicado originalmente en inglés por Andrew Brogdon en el canal de Flutter en youtube. Por favor, visita el siguiente enlace y recomienda el video original si te gusta el contenido:

Futuros

“Hace parte de los Patrones de codificación asíncronos en Dart.”

Future es una de las APIs más básicas que Dart tiene para el manejo de async (asíncrono).

La mayoría de los lenguajes modernos tienen algún tipo de soporte para la programación asíncrona. Muchos ofrecen una API de futuros, y algunos los llaman Promesas. Y en su mayor parte, los Futuros de Dart son muy similares a las que se encuentran en otros lenguajes.

Imagíneselos como pequeñas cajas de regalo para datos. Alguien te da una y viene cerrada.

Luego, un poco más tarde, se abre y adentro, hay un valor o un error.

Así que esos son los tres estados en los que puede estar un Futuro. Primero, la caja está cerrada. A eso lo llamamos incompleto. Luego se abre la caja y se completa con un valor (datos) o con un error.


La mayor parte del código que estás a punto de ver gira en torno a tratar con estos tres estados. Por ejemplo, una de tus funciones tiene un futuro, tienes que decidir:

¿Qué hago si la caja no está abierta todavía?

¿Qué hago cuando se abra más tarde y obtenga un valor?

¿Y qué hay si obtengo un error?

Y así sucesivamente.

Algo bueno sobre los Futuros es que en realidad son sólo una API construida para facilitar el Event Loop.

El código de Dart que escribes es ejecutado por un solo hilo. Todo el tiempo que tu aplicación está funcionando, ese pequeño hilo sigue dando vueltas, recogiendo eventos de la cola de eventos y procesándolos.

Los Futuros trabajan con el Bucle de Eventos para simplificar las cosas. Por ejemplo, supongamos que tienes un código para un botón de descarga.

El usuario pulsa, y comienza a descargar una imagen de un cupcake o cualquier otra cosa. Bueno, primero, se produce el evento tap.

El Bucle de Eventos lo recibe y su manejador de taps es llamado. Utilizas la librería http para hacer una petición y obtienes un Futuro a cambio.

Así que ahora tienes tu cajita, ¿no?

Comienza cerrada, así que tu código usa Then para registrar un callback para cuando se abra. Entonces esperas y tal vez lleguen otros eventos.

El usuario hace algunas cosas, y tu pequeña caja simplemente se sienta allí mientras el Bucle de Eventos sigue dando vueltas. Eventualmente, llegan los datos de la imagen y la biblioteca https dice, ¡genial!, tengo este Futuro aquí mismo. Pone los datos en la caja y la abre, lo que dispara el callback. Ahora ese pequeño trozo de código se ejecuta y muestra la imagen.

A lo largo de ese proceso, tu código nunca tuvo que tocar el Bucle de Eventos directamente. No nos interesa saber qué más estaba pasando, qué otros eventos llegaban. Todo lo que tenía que hacer era conseguir el Futuro de la biblioteca https y luego informar lo que iría a hacer cuando el Futuro se completara.

¿Cómo obtengo una instancia de un futuro?

La mayor parte del tiempo, probablemente no estarás creando Futuros directamente. Esto se debe a que muchas de las tareas de asincronía más comunes ya tienen bibliotecas que generan Futuros para ti, como la comunicación en red, que devuelve un futuro. Por ejemplo, acceder a las preferencias compartidas devuelve un Futuro.

Pero también hay constructores que puedes usar. El más simple es el valor por defecto, que tiene una función y devuelve un futuro con el mismo tipo. Después, ejecuta la función de forma asincrónica y utiliza el valor de retorno para completar el futuro.

Aclaremos la parte asíncrona. Ahora, cuando ejecutes el siguiente código, puedes ver que todo el método principal termina antes de la función que se le dio al constructor del Futuro.

Eso es porque el constructor del Future acaba de regresar un futuro incompleto al principio. Dice: aquí está esta caja, quédate con eso por ahora, y más tarde ve a ejecutar tu función y pon algunos datos ahí para ti.

Si ya conoces el valor para el futuro, puedes usar el valor Future.valuenombrado constructor.

Sin embargo, el futuro todavía se completa asincrónicamente. He usado lo anterior al construir servicios que usan cacheo. A veces ya tienes el valor que necesitas, así que puedes ponerlo justo ahí.

Future.value también tiene una contrapartida por completar con un error, por cierto. Se llama Future.error, y funciona esencialmente de la misma manera. Pero toma un objeto error y un rastreo de pila opcional. El constructor que probablemente sea el que más uso, es Future.delayed. Funciona igual que el predeterminado, sólo espera un tiempo determinado antes de ejecutar la función y completar el futuro. Lo uso todo el tiempo cuando creo servicios de red ficticios para pruebas.

Si necesito asegurarme de que mi pequeño spinner de carga está mostrando correctamente y luego se va, en algún lugar, hay un Futuro retrasado ayudándome.

De acuerdo. Así es que de ahí vienen los Futuros. Ahora hablemos de cómo usarlos. Como mencioné antes, es más que nada sobre la contabilidad de los tres estados en los que puede estar un futuro incompleto, completado con un valor, o completado con un error.

Aquí hay un Future.delayed creando un Future.

que se completará tres segundos después con un valor de 100. Ahora, cuando ejecuto esto, el main() corre de arriba a abajo, crea el Futuro, e imprime “Esperando por un valor…”. Todo ese tiempo, el futuro está incompleto. No se completará hasta dentro de tres segundos. Así que, si quiero usar ese valor, lo usaré hasta entonces.

Este es un método de instancia en cada Futuro que puede utilizar para registrar un callback para cuando el futuro se completa con un valor.

Le das una función que toma un único parámetro que concuerda con el tipo de Futuro y entonces, una vez que el futuro se completa con un valor, su función se ejecuta con ese valor. Así que, si ejecuto esto, aún sigo esperando primero por un valor.

y tres segundos después, mi callback se ejecuta e imprime el valor.

Además, a continuación, devuelve un Futuro propio coincidiendo el valor de retorno de cualquier función que le des.

Así que si tienes unas llamadas asincrónicas que necesitas hacer, puedes encadenarlas juntas, incluso si tienen diferentes tipos de devolución.

Volviendo a nuestro primer ejemplo, sin embargo, ¿qué pasa si ese futuro inicial no se completa con un valor?

¿Qué pasa si se completa con un error? Entonces espera un valor.

Necesitamos una forma de registrar otro callback en caso de error y podrías hacerlo atrapando el error.

Atrapar el error funciona igual que then sólo que toma un error en lugar de un valor,

y se ejecuta, si el Futuro se completa con un error.

Al igual que entonces, devuelve un futuro propio. Así que puedes construir toda una cadena de thens y atrapar errores, y luego atrapar errores y thens, y atrapar errores que esperan unos tras otros.

Incluso puedes darle un método de prueba para verificar el error antes de invocar el callback.

Puedes tener múltiples métodos de captura de error de esta manera, cada uno buscando un tipo de error diferente.

Ahora que hemos llegado tan lejos, espero que tú puedas ver lo que quiero decir sobre cómo los tres estados de un futuro se reflejan a menudo en la estructura del código.

Hay tres bloques aquí. El primero crea un Futuro incompleto. Luego hay una función a la que llamar cuando el futuro se complete con un valor y otra, si se completa con un error.

Sin embargo, tengo un método más para mostrarte, que es cuando se completa.

Puedes utilizarlo para ejecutar un método cuando el futuro se ha completado, no importa si es con un valor o un error. Es como un bloque final en try catch finally.

Hay un código ejecutándose si todo está correcto, hay código para un error, y luego código que se ejecuta sin importar lo que pase.

Así es como se crean los Futuros y un poco de sobre cómo puedes usar sus valores.

Ahora hablemos de ponerlos a trabajar en Flutter. Digamos que tienes un servicio de red que va a devolver algún JSON, y tú quieres mostrarlo. Podrías crear un widget de estado que creará el Future, y que verifique la finalización o el error, llamar a setState y, en general, manejar todo ese cableado manualmente. O puedes utilizar FutureBuilder.

Es un Widget que viene con el SDK de Flutter. Le das un Future y un método constructor y automáticamente reconstruirá a sus hijos cuando el futuro se complete. Lo hace llamando a su método constructor, el cual toma un contexto y un snapshot del estado actual del futuro.

Puedes comprobar el snapshot para ver si el futuro finalizó con un error, comprobando la propiedad hasError y reportarlo.

De lo contrario, puedes comprobar la propiedad hasData para ver si se completó con un valor.

Y si no, sabes que aún estás esperando.

Así que también puedes sacar algo por ahí también. Incluso en el código Flutter, puedes ver cómo esos tres estados siguen apareciendo,

incompleto, completado con valor y completado con error.

La próxima vez, hablaremos de los Streams. Son muy parecidos a los Futures, en el sentido de que pueden proporcionar valores o errores. Pero donde los futuros sólo te dan un valor y se detienen. Los streams siguen su curso.

Así que estén atentos a eso y diríjanse a dart.dev y flutter.dev para más información sobre Dart y Flutter.

1

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Comunidades en Español