Flutter DART Programación asíncrona

De MediaWiki
Ir a la navegación Ir a la búsqueda

Introducción

  • La programación asíncrona hace referencia al uso de threads para ejecutar en modo 'paralelo' al hilo principal otra serie de tareas.



Futures

  • Más información:




void main() {

  print('Inicia programa');
  
  getImagenes('imagenes/animales').then(
    (datos) => print(datos)
  );
  
  print('Fin programa');
  
}

Future<List<String>> getImagenes(String path){
  
  List<String> datos = ['1.jpg','2.jpg','3.jpg','4.jpg'];
  
  
  return Future.delayed(Duration(seconds: 4),() => datos);  // Paramos el hilo durante 4 segundos
  
  
  // return Future.value(datos);
  
}



Gestión de errores

void main() {

  print('Inicia programa');
  
  try {
    getImages('imagenes/animales').then((datos) {
      datos.forEach((elem) => print('Dato:$elem'));
     }).catchError((e) { print(e);} );
    
  }
  catch(e){
    print('Excep:$e');
  }
  print('Fin programa');
  
}

Future<List<String>> getImages(String path){
  
  List<String> imagenes = ['1.jpg','2.jpg','3.jpg'];
  
  // Búsqueda de imágenes a partir del path
  
  //throw('Exepción no controlada');
  
  return Future.delayed(Duration(seconds: 3),() => Future.error('Error controlado'));  
}



Nota: Una de las diferencias entre catchError y onError es que con catchError gestionamos las excepciones que se puedan lanzar dentro del then.

Estado Completo

  • 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.


  • Es decir, el método then se va a ejecutar sólo cuando el Future acabe de ejecutarse correctamente mientras que el método whenComplete se ejecuta tanto si ha ido bien como si ha habido un error.
Veamos un ejemplo:
void main() {

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

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

  print('Fin programa');
  
}
Future<void> cargarImagenesConError(){
  
 return Future.delayed(Duration(seconds: 2),() => throw('Error cargando imágenes')); 
  
}

Future<void> cargarImagenes(){
  
 return Future.delayed(Duration(seconds: 2),() { print('Cargamos imágenes');}); 
  
}
  • 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.
  • El Future cargarImagenesConError no devuelva nada (void) y se ejecuta con errores, por lo tanto, la llamada a la función desde el hilo principal ejecuta el método catchError y el método whenComplete.



Async/Await

  • Más información:
  • 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.
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 estaríamos obligados 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.
void main() {

  print('Inicia programa'); 
  List<String>? datos;
  
  getImages('imagenes/animales').then((datos) {
      print(datos);    
  });
      
  print('Fin programa');
  
}


  • 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).
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 devuelva el dato.
  • 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.


void main() async {   // Definimos como async ya que va a hacer una llamada con await a un Future

  print('Inicia programa'); 
  
  final datos = await getImages('imagenes/animales');  // Esperamos a que termine de ejecutarse.
      
  print('Datos obtenidos después de ejecutar el Future:$datos');  
  print('Fin programa');
  
}

Future<List<String>> getImages(String path)  {
  
  List<String> imagenes = ['1.jpg','2.jpg','3.jpg'];
  
  // Búsqueda de imágenes a partir del path
  
  
  return Future.delayed(Duration(seconds: 3),() => imagenes);
  
}
Nota importante: 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.




  • Dentro del Future, si a este le indicamos que va a ser async (porque dentro del método llamamos a otro Future) ya no hace falta devolver un return Future.value y podremos devolver diretamente el dato con return dato_a_devolver.
Por ejemplo:
Future<String> getDatoLocal(String path) async{

  // Por ejemplo path apunto a un archivo local => 'C:\\temporal\\hosts'
  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)
}




Ejercicios propuestos

Ejercicio 1: Sabrías aplicar los conceptos aprendidos para que el pedido apareza correctamente (ejercicio obtenido de este enlace):

String createOrderMessage() {
  var order = fetchUserOrder();
  return 'Tu pedido es: $order';
}

Future<String> fetchUserOrder() =>
    // Imagine that this function is
    // more complex and slow.
    Future.delayed(
      const Duration(seconds: 2),
      () => 'Café largo',
    );

void main() {
  print('Preparando el pedido...');
  print(createOrderMessage());
  print('Pedido finalizado...');
}



Ejercicio 2:


Apartado A)
  • 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.
Desde la función main, llama a obtenerEdad y el resultado empléalo para llamar a obtenerPremio y muestra el premio.



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.





Solución Ejercicio 2

APARTADO A)

int obtenerEdad(String nombre){
  
  // ACCEDEMOS A INTERNET A BUSCAR LA EDAD
  final alumnos = {'pedro' : 27, 'ana' : 35, 'luisa' : 22};
  
  if (!alumnos.containsKey(nombre)) return 0;
  
  return alumnos[nombre]!;
}

String obtenerPremio(int edad){
  
  // ACCEDEMOS A INTERNET A BUSCAR LOS PREMIOS
  final premios = ['Jamón', 'Coche','Piso'];
  
  return premios[edad%3];
  
}

void main() {
  
  int edad = obtenerEdad('pedro');
  String premio = obtenerPremio(edad);
  
  print('Pedro con la edad de $edad, tiene de premio:$premio');
   

}



APARTADO B)

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]!;});

}

Future<String> obtenerPremio(int edad){
  
  // ACCEDEMOS A INTERNET A BUSCAR LOS PREMIOS
  final premios = ['Jamón', 'Coche','Piso'];
  
  return Future.delayed(const Duration(seconds: 2),(){ print('Obtemos el premio...');
                                                       return premios[edad%3]; });

}

void main() async {
  
  int edad = await obtenerEdad('pedro');
  String premio = await obtenerPremio(edad);
  
  print('Pedro con la edad de $edad, tiene de premio:$premio');
  
}




Enlace a la página principal de la UD2

Enlace a la página principal del curso




-- Ángel D. Fernández González -- (2021).