Promesas

<< Modulos AJAX / Fetch >>

Una promesa es un objeto que se crea para resolver una acción asíncrona, es decir, una operación que puede tardar un tiempo considerable y no queremos bloquear el hilo principal del programa mientras se procesa. El constructor recibe una función con dos parámetros, resolve y reject. resolve se llama cuando la acción acaba correctamente devolviendo (opcionalmente) algún dato, mientras que reject se usa cuando se produce un error. Una promesa acaba cuando se llama a una de estas dos funciones.

Para procesar el resultado de una promesa, tenemos que suscribirnos a ella llamando al método then, pasándole una función que manejará el resultado. Esta función se ejecutará cuando se resuelva la promesa correctamente (sin errores).

La llamada al método then a su vez devuelve otra promesa. Es por ello que podremos encadenar varias llamadas a dicho método. El primero recibe el resultado de la promesa original, mientras que los restantes reciben como resultado lo que devuelva la función del método then anterior. Pueden devolver cualquier tipo de valor, incluyendo otra promesa. El siguiente then recibiría el resultado de dicha promesa una vez se resuelva.

Iremos directamente al bloque catch si lanzamos un error dentro de una sección then. O si la promesa original se rechaza con reject.

Opcionalmente, por legibilidad o reutilización, podemos separar las funciones del bloque then (o catch) en lugar de usar funciones anónimas o arrow functions.

Tanto el método then, como el método catch, pueden devolver un valor directamente u otra promesa. Esto significa que podemos usar el método catch para recuperarnos de un error, obteniendo datos de otro sitio alternativo, por ejemplo. Lo que devuelve un método catch se recogería en un método then que se concatene posteriormente.

Hay otro método llamado finally, que se puede añadir al final y se ejecuta siempre, haya error o no. Se puede usar por ejemplo para ocultar una animación de carga de datos.

Promise.resolve y Promise.reject

Cuando una función devuelve una Promesa (llamada a un servicio web por ejemplo), a veces tiene el valor disponible y se puede ahorrar dicha llamada. Como la función se espera que devuelva una promesa, para matener la consistencia de tipos, se puede devolver un valor inmediatamente encapsulado en una promesa con Promise.resolve, o una promesa con error usando Promise.reject. De esta forma nos ahorramos crearnos una promesa manualmente llamando al constructor.

Otros métodos de Promise

Promise.all

A veces tenemos que crear varias promesas en paralelo (varias llamadas a un servidor para obtener diferentes datos). Si necesitamos esperar para hacer algo una vez todas terminen en lugar de procesarlas por separado, podemos agruparlas en un array y utilizar el método Promise.all. Una vez haya terminado la última, se ejecutará el método then asociado que recibirá un array con los resultados de cada promesa.

Si alguna de estas promesas es rechazada (error), se ignorará inmediatamente a las promesas pendientes, ejecutando directamente el método catch (si lo hubiera).

Promise.allSettled

Parecido al método anterior. Si queremos esperar a que todas las promesas acaben, aunque una o varias devuelvan un error, tenemos el método Promise.allSettled. En este caso, el método then recibirá un array de objetos (1 por promesa). Cada objeto tendrá 3 posibles propiedades:

  • status: Contendrá los valores "fulfilled" (todo ha ido bien), o "rejected" (error).
  • value: Cuando la promesa tenga el estado "fulfilled", contendrá el valor devuelto.
  • reason: Cuando la promesa tenga el estado "rejected", contendrá el error.

Promise.race

Otro método interesante es Promise.race. En este caso, devuelve el resultado de la primera promesa que termina, ignorando el resto. Útil, por ejemplo, si podemos obtener un dato de varias fuentes diferentes, y queremos la más rápida.

Async / Await

Las instrucciones async y await fueron introducidas en la versión ES2017. Se utilizan para que la sintaxis al trabajar con promesas sea más amigable.

await se utiliza para esperar que una promesa termine y nos devuelva su valor. El problema es que eso bloquea la ejecución del siguiente código hasta que la promesa acabe. Esto implicaría bloquear el programa durante un tiempo indeterminado. Por ello, sólo podemos usarlas dentro de funciones de tipo async. Estas funciones se ejecutan de forma asíncrona al resto del código, lo que impide que bloqueen el hilo principal.

Una función async siempre nos devuelve una promesa, que contendrá el valor devuelto por la instrucción return. Si no hay instrucción return, también devuelve una promesa, aunque vacía (Promise<void>).

Import dinámico con promesas

Además de la instrucción import, se puede importar un módulo con la función import(). Esta función devuelve una promesa que se resuelve una vez que el módulo ha sido descargado y procesado, y nos devuelve un objeto que contiene todo lo que el módulo exporta. Esto nos permite hacer imports dinámicos, donde el nombre del módulo puede estar en una variable o simplemente, no cargar un módulo hasta que realmente lo necesitemos por cuestiones de eficiencia y rendimiento.

Desde ES2020 tenemos top-level await, lo que significa que no tenemos que crear funciones async para poder utilizar await en el código principal. Pero debemos recordar que que el resto del código se bloqueará hasta que se resuelva la instrucción await (en este caso la carga del módulo). Esto solo debemos utilizarlo si el resto del código en el módulo actual es totalmente dependiente del resultado de la promesa (en este caso del módulo que cargamos). Si tienes alguna duda, intenta evitarlo de todos modos.

<< Modulos AJAX / Fetch >>