| Ambos lados, revisión anteriorRevisión previaPróxima revisión | Revisión previa |
| informatica:programacion:cursos:programacion_avanzada_javascript:funciones_asincronas [2024/10/16 10:49] – [DEMO: Más sobre async y promises] tempwin | informatica:programacion:cursos:programacion_avanzada_javascript:funciones_asincronas [2024/10/30 12:58] (actual) – [Ejercicio propuesto] tempwin |
|---|
| Las funciones asíncronas se invocan exactamente igual que las funciones tradicionales. Es decir, la función anterior la podemos invocar usando simplemente ''answerAsync()''. | Las funciones asíncronas se invocan exactamente igual que las funciones tradicionales. Es decir, la función anterior la podemos invocar usando simplemente ''answerAsync()''. |
| |
| <WRAP center round todo 60%> | {{ :informatica:programacion:cursos:programacion_avanzada_javascript:12-invocacion-funcion-asincrona.png |}} |
| invocación función asíncrona | |
| </WRAP> | |
| |
| Si nos fijamos en la imagen anterior, observamos cómo al invocar la función ''answerAsync'' el resultado devuelto no ha sido ''undefined'', que es lo que devolvería la versión síncrona/convencional de esa misma función (recuerda que las funciones que no devuelven nada explícitamente devuelven ''undefined''), sino que la función devuelve una promise. En este caso la promise está resuelta al terminar la llamada. | Si nos fijamos en la imagen anterior, observamos cómo al invocar la función ''answerAsync'' el resultado devuelto no ha sido ''undefined'', que es lo que devolvería la versión síncrona/convencional de esa misma función (recuerda que las funciones que no devuelven nada explícitamente devuelven ''undefined''), sino que la función devuelve una promise. En este caso la promise está resuelta al terminar la llamada. |
| En este caso, una vez la función asíncrona se ha ejecutado, se ejecuta la función indicada dentro del ''then'': | En este caso, una vez la función asíncrona se ha ejecutado, se ejecuta la función indicada dentro del ''then'': |
| |
| <WRAP center round todo 60%> | {{ :informatica:programacion:cursos:programacion_avanzada_javascript:12-invocacion-funcion-asincrona-then.png |}} |
| invocación función asíncrona y then | |
| </WRAP> | |
| |
| Por lo tanto toda función ''async'' devuelve una //promise//. El resultado devuelto por la función es el resultado que se resuelve con la //promise//. | Por lo tanto toda función ''async'' devuelve una //promise//. El resultado devuelto por la función es el resultado que se resuelve con la //promise//. |
| ===== Errores en funciones asíncronas ===== | ===== Errores en funciones asíncronas ===== |
| |
| | Una de las facilidades de usar ''async''/''await'' es que permite mediante ''try''/''catch'' capturar tanto errores en funciones síncronas como en funciones asíncronas. Usando //promises// eso no es posible: |
| | |
| | <code javascript> |
| | function asyncPromise() { |
| | return new Promise((resolve, reject) => { |
| | reject("Unspecified error") |
| | }); |
| | } |
| | |
| | try { |
| | asyncPromise().then(() => { |
| | console.log('asyncPromise run!'); |
| | }); |
| | } |
| | catch (error) { |
| | console.err('asyncPromise failed with: ' + error); |
| | } |
| | </code> |
| | |
| | **Usando ''try''/''catch'' no se capturan las promesas rechazadas**. Así, a pesar de que ''asyncPromise'' ha fallado, nosotros no capturamos el error: |
| | |
| | {{ :informatica:programacion:cursos:programacion_avanzada_javascript:12-error-promise-no-capturado.png |}} |
| | |
| | La imagen muestra el resultado de ejecutar dicho código; en Firefox a la izquierda y en Chrome a la derecha. Observa cómo el ''catch'' no es invocado nunca (Firefox, a diferencia de Chrome, se queja de que una //promise// rechazada no ha sido tratada). |
| | |
| | Como ya sabemos, para capturar errores en //promises// debemos usar el ''catch'' de la propia //promise//. Eso implica que si mezclamos llamadas a funciones asíncronas (que devuelvan //promises//) y funciones síncronas, el tratamiento de errores lo tenemos disperso: el ''catch'' del bloque ''try''/''catch'' nos captura los errores síncronos, y la función ''catch'' de la //promise// los errores en la //promise// rechazada. |
| | |
| | Recuerda además, que dentro de una //promise// lanzar un error, rechaza la //promise//, por lo que en el siguiente código **el bloque ''catch'' tampoco se ejecuta**: |
| | |
| | <code javascript> |
| | function asyncPromise() { |
| | return new Promise((resolve, reject) => { |
| | throw ("Unspecified error") |
| | }); |
| | } |
| | |
| | try { |
| | asyncPromise().then(() => { |
| | console.log('asyncPromise run!'); |
| | }); |
| | } |
| | catch (error) { |
| | console.error('asyncPromise failed with: ' + error); |
| | } |
| | </code> |
| | |
| | Ahora, a pesar de usar ''throw'' en ''asyncPromise'' el resultado es exactamente el mismo que antes: la función ''asyncPromise'' termina devolviendo una //promise// rechazada y el bloque ''catch'' no se llega a invocar. |
| | |
| | ==== Bloques try/catch y async/await ==== |
| | |
| | El uso de ''async''/''await'' soluciona esos problemas: |
| | |
| | <code javascript> |
| | function asyncPromise() { |
| | return new Promise((resolve, reject) => { |
| | throw ("Unspecified error") |
| | }); |
| | } |
| | |
| | async function test() { |
| | try { |
| | await asyncPromise(); |
| | console.log('asyncPromise run!'); |
| | } |
| | catch (error) { |
| | console.error('asyncPromise failed with: ' + error); |
| | } |
| | } |
| | |
| | test(); |
| | </code> |
| | |
| | Observa que **no se ha modificado ''asyncPromise()''** que sigue siendo una función tradicional que devuelve una //promise//. Pero ahora, se ha creado ''test()'' que es una función asíncrona (usa ''async'') y realiza una espera asíncrona (''await'') sobre ''asyncPromise()''. Pues bien, si ''asyncPromise()'' devuelve una //promise// rechazada, la espera asíncrona finaliza con error y el bloque ''catch'' se ejecuta. |
| | |
| | Por lo tanto, al usar ''async''/''await'' es posible el **uso de bloques ''try''/''catch'' para centralizar toda la gestión de errores**, sean estos producidos por llamadas síncronas o asíncronas. Eso permite que el código sea mucho más claro. |
| ===== Esperas asíncronas en paralelo ===== | ===== Esperas asíncronas en paralelo ===== |
| |
| | El siguiente código muestra una función que devuelve una //promise// que se resuelve al cabo de ''sec'' segundos: |
| | |
| | <code javascript> |
| | function timeout(sec) { |
| | return new Promise(resolve => { |
| | setTimeout(() => { resolve(x);}, sec * 1000); |
| | }); |
| | } |
| | |
| | ¿Cuánto tiempo tarda en ejecutarse el siguiente código? |
| | |
| | async function test() { |
| | await timeout(2); |
| | await timeout(3); |
| | console.log('¡Fin!'); |
| | } |
| | </code> |
| | |
| | Piénsalo un poco antes de responder: el código realiza dos esperas asíncronas por la función timeout. Una espera es de 2 segundos y la otra es de 3. ¿Cuánto tiempo estamos esperando? |
| | |
| | La respuesta correcta es **cinco**. El primer ''await'' hace que nuestra función ''test'' se espere asíncronamente a la resolución de la //promise// que devuelve ''timeout(2)'', lo que no ocurre hasta que transcurren 2 segundos. Solo entonces, cuando la primera espera asíncrona ha sido finalizada, la ejecución de ''test'' continúa y lo hace con otra espera asíncrona de 3 segundos. Por lo que al cabo de unos 5 segundos, aparece el mensaje ''¡Fin!'' en la consola. |
| | |
| | Es decir, varios ''awaits'' sucesivos se ejecutan uno detrás de otro, no en paralelo. Pero... ¿existe alguna manera de realizar esperas asíncronas en paralelo? |
| | |
| | Pues la respuesta es que sí, y pasa por tener presente que ''await'' espera por una //promise//, así que **nos basta con crear una //promise// que ejecute en paralelo dos o mas //promises//** y que se resuelva tan pronto se resuelvan las otras a las que envuelve. Y si recuerdas el módulo de //promises//, cuando mencionamos la composición de //promises// hablamos sobre ''Promise.all'': |
| | |
| | <code javascript> |
| | async function test() { |
| | await Promise.all([timeout(2), timeout(3)]); |
| | console.log('¡Fin!'); |
| | } |
| | </code> |
| | |
| | Ahora el ''await'' espera por la promesa devuelta por ''Promise.all'' que ejecuta en paralelo las dos //promises// y se resuelve cuando ambas son resueltas, lo que ocurre aproximadamente al cabo de, esta vez sí, tres segundos. |
| ===== Ejercicio propuesto ===== | ===== Ejercicio propuesto ===== |
| |
| | Crea una función que, usando la API ''fetch'', devuelva una //promise// resuelta con el número de carácteres de la respuesta de una URI. La función debe invocarse como sigue: |
| | |
| | <code javascript> |
| | uriSize('https://jsonplaceholder.typicode.com/posts/42') |
| | .then(s => console.log('Num carácteres recibidos:' + s)) |
| | </code> |
| | |
| | Crea la función asíncrona ''getLargestUri'' que espere un array de cadenas con varias direcciones URI y que devuelva la que ofrezca la respuesta más larga en bytes. Para sus cálculos haz que use la función ''uriSize'' anterior. Debe invocarse como sigue: |
| | |
| | <code javascript> |
| | getLargestUri([ |
| | 'https://jsonplaceholder.typicode.com/posts/4', |
| | 'https://jsonplaceholder.typicode.com/posts/5', |
| | 'https://jsonplaceholder.typicode.com/posts/42']) |
| | .then(v => console.log('La url más larga es ' + v.url + ' con ' + v.size + ' caracteres')); |
| | </code> |
| | |
| | Intenta que la función ''getLargestUri'' haga las peticiones en paralelo: |
| | |
| | {{ :informatica:programacion:cursos:programacion_avanzada_javascript:12-ejercicio-peticiones-paralelo.png |}} |
| | ==== Consejos ==== |
| | |
| | En contra de lo que pueda parecer, este es un **ejercicio relativamente complejo**, así que tómatelo con calma y repasa lo necesario del módulo de fetch API. |
| | |
| | La función ''getLargestUri'' es la que presenta el mayor reto. Repasa la sección de "Esperas asíncronas en paralelo" para ver lo que debes hacer para llamar asíncronamente N veces la función ''uriSize'' pero que se ejecuten todas en paralelo. |
| | |
| | ==== Y un pequeño reto adicional ==== |
| | |
| | En la función ''getLargestUri'' al final, de los N resultados devueltos por las N llamadas a ''uriSize'' debes seleccionar aquella cuyo valor de ''size'' sea mayor. Seguramente hayas implementado (o tengas pensado implementar) eso mediante un bucle. No hay problema, pero ya puestos te propongo un pequeño reto adicional: |
| | |
| | Crea una función ''max'' que se pueda llamar sobre **cualquier array**. Dicha función debe esperar una función transformadora (que transforma el elemento del array en un número) y debe devolver el elemento cuyo valor transformado en número es mayor. La función debe invocarse de este modo: |
| | |
| | <code javascript> |
| | [{v:42, name:'great question'}, {v: 0, name: 'zero'}].max(i => i.v) |
| | </code> |
| | |
| | (En este caso debe devolver el primer elemento) |
| |
| | Una vez tengas esa función ''max'', úsala en ''getLargestUri'' para seleccionar el elemento con el tamaño mayor. |