Herramientas de usuario

Herramientas del sitio


informatica:programacion:cursos:programacion_avanzada_javascript:promises

Diferencias

Muestra las diferencias entre dos versiones de la página.

Enlace a la vista de comparación

Ambos lados, revisión anteriorRevisión previa
Próxima revisión
Revisión previa
informatica:programacion:cursos:programacion_avanzada_javascript:promises [2024/10/15 11:27] – [Composición de promises] tempwininformatica:programacion:cursos:programacion_avanzada_javascript:promises [2024/10/30 15:52] (actual) – [DEMO: Errores en promises] tempwin
Línea 211: Línea 211:
 </code> </code>
  
-<WRAP center round todo 60%> +{{ :informatica:programacion:cursos:programacion_avanzada_javascript:10-then-devuelve-promise-01.png |}}
-then devuelve una promise +
-</WRAP>+
  
  
Línea 227: Línea 225:
 </code> </code>
  
-<WRAP center round todo 60%> +{{ :informatica:programacion:cursos:programacion_avanzada_javascript:10-then-devuelve-promise-error.png |}}
-then devuelve una promise con error +
-</WRAP>+
  
 Y el tercer ejemplo muestra que, si la función que se pasa a ''then'' devuelve una //promise//, la promise devuelta por ''then'' se resuelve en cuanto se resuelve la //promise// devuelta por la función que se pasa a ''then'': Y el tercer ejemplo muestra que, si la función que se pasa a ''then'' devuelve una //promise//, la promise devuelta por ''then'' se resuelve en cuanto se resuelve la //promise// devuelta por la función que se pasa a ''then'':
Línea 249: Línea 245:
 Hasta que no se resuelva la //promise// "interna" (cuando pasan los 5 segundos del ''setTimeout'') no se resuelve ''p1'' (la //promise// devuelta por el ''then''), de ahí que salga primero el mensaje "//promise inside then is resolved//" y luego el mensaje "//p1 resolved too//": Hasta que no se resuelva la //promise// "interna" (cuando pasan los 5 segundos del ''setTimeout'') no se resuelve ''p1'' (la //promise// devuelta por el ''then''), de ahí que salga primero el mensaje "//promise inside then is resolved//" y luego el mensaje "//p1 resolved too//":
  
-<WRAP center round todo 60%> +{{ :informatica:programacion:cursos:programacion_avanzada_javascript:10-then-devuelve-promise-interna.png |}}
-la promise de then depende de la promise interna +
-</WRAP>+
  
 Con estos ejemplos espero que quede más claro cómo funciona el encadenamiento de //promises// y cómo el método ''then'' devuelve siempre una promise. Con estos ejemplos espero que quede más claro cómo funciona el encadenamiento de //promises// y cómo el método ''then'' devuelve siempre una promise.
Línea 453: Línea 447:
     console.log("Iniciando tarea 1");     console.log("Iniciando tarea 1");
     return demo(100).then(function(r) {     return demo(100).then(function(r) {
-        console.log("En task1() el resultado es ', r);+        console.log("En task1() el resultado es ", r);
     });     });
 } }
Línea 464: Línea 458:
     console.log("Iniciando tarea 1");     console.log("Iniciando tarea 1");
     return demo(100).then(function(r) {     return demo(100).then(function(r) {
-        console.log("En task1() el resultado es ', r);+        console.log("En task1() el resultado es", r);
         return r;         return r;
     });     });
Línea 487: Línea 481:
     console.log("Iniciando tarea 1");     console.log("Iniciando tarea 1");
     return demo(100).then(function(r) {     return demo(100).then(function(r) {
-        console.log("En task1() el resultado es ', r);+        console.log("En task1() el resultado es", r);
         return r;         return r;
     });     });
Línea 547: Línea 541:
 </code> </code>
  
-<WRAP center round todo 60%> +{{ :informatica:programacion:cursos:programacion_avanzada_javascript:10-promise-all.png |}}
-caja de TODO +
-</WRAP>+
  
 +===== DEMO: Composición de Promises =====
 +
 +Veremos cómo podemos ejecutar //promises// todas a la vez, de forma paralela.
 +
 +Partimos de:
 +
 +<code javascript>
 +var demo = function(res) {
 +    var pr = new Promise(function(resolve, reject) {
 +        if (res < 0) {
 +            reject(new Error("El resultado debe ser positivo"));
 +        }
 +        window.setTimeout(function() { resolve(res)}, 2000);
 +    });
 +    
 +    return pr;
 +}
 +
 +var task1 = function() {
 +    console.log("Iniciando tarea 1");
 +    return demo(100).then(function(r) {
 +        console.log("En task1() el resultado es ', r);
 +        return r;
 +    });
 +}
 +
 +var task2 = function(rp) {
 +    console.log("El resultado previo es ->" , rp);
 +    console.log("Iniciando tarea 2");
 +    return demo(2000);
 +}
 +</code>
 +
 +<code javascript>
 +console.log("Inicio");
 +Promise.all([task1(), task2()]).then(function () {
 +    console.log("Fin", arguments[0]);
 +});
 +</code>
 +
 +''arguments[0]'' es un array con los resultados de todas las //promises// que forman la //promise// paralela.
 +
 +<WRAP center round tip 60%>
 +Esto va muy bien para sincronizar peticiones AJAX. Este método nos ahorra utilizar contadores o variables globales.
 +</WRAP>
  
 ===== Captura de errores en promises ===== ===== Captura de errores en promises =====
  
 +Cuando una //promise// entra en estado de error se invoca a su método ''catch''. El método ''catch'' es el equivalente a ''then'', pero se ejecuta cuando la //promise// entra en estado de error en lugar de cuando se resuelve.
 +
 +La función ejecutora de una //promise// utiliza su segundo parámetro (usualmente llamado ''reject'') para pasar la //promise// a estado de error:
 +
 +<code javascript>
 +var errored = new Promise(function(resolve, reject) {
 +    reject(new Error("Some error hapened"));
 +});
 +console.log(errored);
 +</code>
 +
 +En este código la promise ''errored'' está en estado de error. Eso significa que su método ''then'' no se va a ejecutar nunca (dado que no está resuelta ni se va a poder resolver). El método que se va a ejecutar es el método ''catch'':
 +
 +<code javascript>
 +errored.catch(function(err) {
 +    console.log(err);
 +});
 +</code>
 +
 +{{ :informatica:programacion:cursos:programacion_avanzada_javascript:10-promise-catch.png |}}
 +
 +El método ''catch'' comparte todas las otras características del método ''then'':
 +
 +  * Si ejecutas ''catch'' sobre una //promise// que YA está en estado de error, el método se ejecuta al momento.
 +  * La función que se pasa como parámetro a ''catch'' recibe como parámetro el valor que la función ejecutora de la //promise// pasó a ''reject''. (Observa como en el ejemplo, el valor del parámetro ''err'' es el objeto ''Error'' que se ha pasado como parámetro al ''reject'').
 +  * El método ''catch'' devuelve otra //promise// cuyo estado depende de la función que se pase a este ''catch'' (con exactamente las mismas normas que en el caso del método ''then'').
 +
 +===== DEMO: Errores en promises =====
 +
 +Vamos a forzar un error, es decir, que se devuelva una promise en estado de error.
 +
 +<code javascript>
 +var demo = function(res) {
 +    var pr = new Promise(function(resolve, reject) {
 +        if (res < 0) {
 +            // Pasamos la promise a estado de error
 +            reject(new Error("El resultado debe ser mayor que cero"));
 +        }
 +        // Por si se generase algún otro tipo de error, lo capturamos
 +        try {
 +            window.setTimeout(function() { resolve(res)}, 2000);
 +        }
 +        catch (e) {
 +            reject(e);
 +        }
 +    });
 + 
 +    return pr;
 +}
 +</code>
 +
 +Creamos la tarea y capturamos el error:
 +
 +<code javascript>
 +var task = demo(-10); // Forzamos el error
 +
 +// Nunca va ejecutará porque la promise no
 +// pasará a estado resuelto
 +task.then(function(r) {
 +    console.log("Recibido -> ", r);
 +}
 +
 +// Capturamos el error
 +task.catch(function(e) {
 +    console.log("Error recibido -> ", e);
 +});
 +
 +console.log("Fin");
 +</code>
 +
 +===== ¿Cómo limitar el tiempo máximo de ejecución de una Promise? =====
 +
 +Si la API que usamos dentro de la //Promise// ofrece algún mecanismo de //timeout//, lo ideal es usarlo, pero si no es el caso (como curiosamente ocurre con ''fetch'') existe un mecanismo muy sencillo para conseguirlo: usar ''Promise.race''. Esta función toma un iterable de //promises// y devuelva otra promesa que se resuelve/rechaza en cuanto una de las promesas del iterable se resuelva o se rechace.
 +
 +Por lo tanto el mecanismo es muy sencillo. Esta función toma una Promise y devuelve otra con un timeout:
 +
 +<code javascript>
 +const TimeoutPromise = (pr, timeout) =>
 +  Promise.race([pr, new Promise((_, rej) =>
 +    setTimeout(rej, timeout)
 +  )]);
 +</code>
 +
 +Su uso es muy sencillo:
 +
 +<code javascript>
 +let pr = fetch('http://slowwly.robertomurray.co.uk/delay/8000/url/http://www.google.co.uk', {mode: 'no-cors'});
 +let tpr = TimeoutPromise(pr, 500)
 +  .then(() => console.log('fetch done'))
 +  .catch(() =>console.log('timeout cancelled'));
 +</code>
 +
 +La //promise// ''pr'' es una promise que tarda 8 segundos en resolverse (es lo que tarda en cargar esa página), pero si ejecutas ese código verás que al cabo de 500ms la promise ''tpr'' es rechazada.
 +
 +Más fácil imposible, ¿verdad?
 +
 +Bien, nada es perfecto: este método cuando se rechaza la promise por el timeout, **el resto de promises internas siguen ejecutándose**. Es decir, en nuestro caso, tpr es rechazada al cabo de 500ms, pero la promise pr se sigue ejecutando durante los 8s (hasta que se completa la petición de red). Por lo tanto **este mecanismo no aborta promises**. Se limita a envolverlas con una promesa que, esta sí, se resuelve/rechaza en un tiempo máximo.
 +
 +Lo ideal sería poder cancelar la promise, pero ECMAScript no ofrece un mecanismo para poder hacerlo, aunque [[https://github.com/tc39/proposal-cancellation|el TC39 está trabajando en ello]].
 +
 +==== "Cancelando" promises ====
 +
 +Existen [[https://medium.com/@benlesh/promise-cancellation-is-dead-long-live-promise-cancellation-c6601f1f5082|otras técnicas]], pero si para ti es vital el poder "cancelar promises" aquí tienes un código muy sencillo que permite **interrumpir una promise en cada uno de sus pasos**. Cada uno de esos pasos debe ser a su vez una promise:
 +
 +<code javascript>
 +const token = function() { 
 +  return {
 +    cancel: function () {this.isCancelled = true},
 +    isCancelled: false
 +    }
 +}
 +async function  MultistepPromise (iterf, token)
 +{
 +  for (let f of iterf) {
 +    await f();
 +    if (token.isCancelled)  {
 +      throw 'promise cancelled';
 +    }
 +  }
 +}
 +</code>
 +
 +Bueno, como puedes ver el código es trivial: simplemente itera por el iterable de promises si por alguna razón el token se cancela.
 +
 +Su uso es, de nuevo, muy sencillo:
 +
 +<code javascript>
 +const func = () => fetch(
 +  'http://slowwly.robertomurray.co.uk/delay/3000/url/http://www.google.co.uk',  
 +  {mode: 'no-cors'});
 +const tok = token();
 +const pr = MultistepPromise([func, func, func, func, func, func], tok)
 +  .then(() => console.log('all acceoted'))
 +  .catch((e) => console.log('some error', e));
 +</code>
 +
 +Si una vez ''pr'' se está ejecutando, llamas a ''tok.cancel()'', entonces ''pr'' se cancelará una vez se haya finalizado el paso correspondiente (a medio paso no se puede cancelar).
 +
 +Observa que ''MultistepPromise'' no espera un array de promises, sino simplemente un iterable de promises, por lo que puedes usar otras técnicas tales como un generador para pasarle las promises a ejecutar.
 +
 +Por supuesto, puedes combinar ambas técnicas: es decir, hacer una ''MultistepPromise'', donde cada uno de sus pasos sea ''TimeoutPromise''. De este modo te aseguras que si uno de los pasos excede el tiempo, todo el resto de pasos se cancelan (no se ejecutan).
 +===== Recursos =====
  
 +  * [[https://promisesaplus.com/|Especificación oficial de Promises/A+]]
 +  * [[https://github.com/stefanpenner/es6-promise|Polyfill de Promises/A+]]
 +  * [[https://dmitripavlutin.com/javascript-promises-then-vs-then-catch/|JavaScript Promises: then(f,f) vs then(f).catch(f)]]
informatica/programacion/cursos/programacion_avanzada_javascript/promises.1728984448.txt.gz · Última modificación: por tempwin