Módulo perteneciente al curso Programación avanzada con JavaScript y ECMAScript.
Las promises (promesas) ya hace algún tiempo que se vienen utilizando en el desarrollo JavaScript, aunque no es hasta ECMAScript 2015 cuando se incorporan “de serie” en el lenguaje. De todos modos, si debes soportar algún navegador que no las incorpora (básicamente IE en cualquier versión, Edge -incluido en Windows 10- sí que las soporta) puedes usar algún polyfill.
La especificación de Promises en ECMAScript 2015 es conocida como Promises/A+
Una promise es, como su propio nombre indica, una promesa: una promesa de que cierta tarea se finalizará en algún momento. Así pues, debes ver a las promises como tareas “que serán ejecutadas en algún momento dado”. Esto incluye el pasado, es decir, una promise puede representar una tarea realizada, realizándose o por realizar (pero que se realizará).
A priori puede costar ver la necesidad de tener algo como las promises, pero para ver su potencial es necesario entender antes el concepto de la “pirámide de callbacks”.
Tomemos como punto de partida el siguiente código, ¡a ver si encuentras qué tiene de malo!:
navigator.geolocation.getCurrentPosition(function(c) { console.log(c); } );
La respuesta es: nada. Este código no tiene nada de malo. Estamos llamando al método getCurrentPosition y pasándole una función de callback. Hacemos esto continuamente en JavaScript. El problema puede venir si dentro de la función de callback tenemos que llamar a otra función que espera a su vez un callback, tal y como sucede en el siguiente código:
navigator.geolocation.getCurrentPosition(function(c) { console.log(c); window.setTimeout(function() { navigator.geolocation.getCurrentPosition(function(c2) { console.log(c2); }); }, 2000); } );
callback: Un método que se llama automáticamente al terminar de ejecutarse otro método anterior. Por ejemplo, se llama a una función que procesa unos datos y cuando ésta termina, llama a un método que le hayamos indicado en sus argumentos, para iniciar un proceso diferente o para indicar que ha finalizado.
Observa que dentro del callback del primer getCurrentPosition se llama a setTimeout que recibe otro callback, y a su vez dentro de este segundo callback se llama otra vez a getCurrentPosition y se le pasa una tercera función de callback.
Si te fijas en la estructura de código, debido a la indentación, parece una pirámide con la punta hacia la derecha. Esta sucesión de unos callbacks dentro de otros una y otra vez es lo que se conoce como la “pirámide de callbacks” y puede llegar a extremos enfermizos:
Las promises pueden ayudar a solventar este problema, aunque se debe evitar caer en la “pirámide de promises”, que también existe. Pero también veremos otras posibilidades que ofrecen.
Vamos a ver cómo el uso de promises nos permite desmontar la pirámide de callbacks que teníamos en el ejemplo anterior. Para ello vamos a suponer que las APIs que usábamos están implementadas usando promises. Simplemente, ten presente que esto no es cierto: no hay todavía APIs nativas en JavaScript que estén implementadas usando promises.
Para este ejemplo vamos a suponer que existen las funciones getCurrentPositionPromise y setIntervalPromises que hacen lo mismo que las funciones reales getCurrentPosition y setInterval pero usando promises. Más adelante veremos cómo podemos crear realmente esas funciones.
Una API basada en promises en lugar de esperar un callback devuelve una promise. Así pues, nuestro supuesto método getCurrentPosition no esperaría un parámetro con la función de callback, sino que devolvería una promise, por lo que sería llamado de la siguiente manera:
var pr = navigator.geolocation.getCurrentPositionPromise();
El valor devuelto (pr) es la promise. Recuerda que una promise representa una tarea que ya puede estar hecha o que se realizará en un tiempo futuro. Para saber el estado de la tarea debemos consultar el estado de la promise, el cual puede ser:
pending): La tarea no se ha ejecutado y no hay resultado alguno.fulfilled): La tarea ha sido completada y por lo tanto hay un resultado obtenido. Este resultado (que puede ser undefined) queda asociado a la promise.rejected): Ha habido un error ejecutando la tarea y por lo tanto no hay resultado alguno que podamos consultar.En realidad no existe ninguna propiedad que nos diga el estado en el que se encuentra una promise. En su lugar se usan dos métodos de la promise: el método then y el método catch.
El primero, then, recibe como parámetro una función, que se ejecutará en cuanto la tarea sea completada sin error. Por su parte, el segundo método, catch, recibe como parámetro una función que se ejecutará si hay algún error al ejecutar la tarea.
Para entendernos, cuando la promise pasa de estado “Pendiente” a “Completada” se ejecutará la función pasada como parámetro en el método then, y cuando la promise pasa de “Pendiente” a “Error” se ejecutará la función pasada como parámetro en el catch.
Pero no solo eso, si llamas a then o a catch de una promise que ya se encuentra en estado “Completada” o “Error”, se ejecutará la función pasada correspondiente. Es decir, da igual si la tarea representada por la promise ha terminado o no cuando llames a then: si no lo ha hecho, tan pronto termine se ejecutará la función parámetro del then, y si ya había finalizado entonces se ejecutará de inmediato la función pasada como argumento en el then.
Por lo tanto para simular el ejercicio anterior con promises usaríamos un código similar a:
var pr = navigator.geolocation.getCurrentPositionPromise(); pr.then(function(coords) { console.log(coords); var pr2 = setIntervalPromises(2000); pr2.then(function() { var pr3 = navigator.geolocation.getCurrentPositionPromise(); pr3.then(function(coords2) { console.log(coords2); }); }); });
Observa que la función que se le pasa al then recibe como argumento el valor devuelto por la tarea representada por la promise.
Este código, sin embargo, no es mucho mejor que el anterior. Es cierto que ahora no tenemos una “pirámide de callbacks”, pero tenemos una “pirámide de promises”. Esta otra pirámide se da cuanto tenemos thens dentro de thens dentro de thens de forma sucesiva (en este caso encadenamos tres thens uno dentro del otro). Eso es porque en este ejemplo estamos usando mal las promises.
Evita siempre caer en la pirámide de promises: tiene los mismos problemas de legibilidad y mantenibilidad que la pirámide de callbacks.
Para poder romper la pirámide de promises el primer paso es separar cada una de las tareas en funciones separadas:
// Tarea 1: Obtener la posición (e imprimirla en consola) var task1 = function() { var pr = navigator.geolocation.getCurrentPositionPromise(); pr.then(function(c) { console.log('1ª coord -> ', c); }); return pr; } // Tarea 2: Esperar 2000ms (y imprimir por consola una vez han pasado) var task2 = function() { var pr = setIntervalPromises(2000); pr.then(function() { console.log('2000 ms de espera'); }); return pr; } // Tarea 3: Obtener la posición (e imprimirla en consola) var task3 = function() { var pr = navigator.geolocation.getCurrentPositionPromise(); pr.then(function(c) { console.log('2ª coord -> ', c); }); return pr; }
Observa que las funciones hacen lo que tengan que hacer, pero devuelven una promise, puesto que representan tareas. En este caso nos limitamos a devolver la misma promise que nos devuelven los métodos getCurrentPositionPromise y setIntervalPromises.
Ahora necesitamos encadenar las 3 tareas una tras otra. Para ello, no debemos usar un código como el siguiente:
var pr = task1().then( function() { task2().then(function() { task3(); }); });
¡Este código funciona correctamente pero sigues teniendo la pirámide de promises! El mero hecho de tener las tareas separadas en funciones no te sirve para evitar la pirámide de promises si luego vuelves a encadenar los thens uno dentro de otro.
Para poder romper la pirámide de promises debes tener presente una cosa: los métodos then se pueden encadenar entre ellos. De hecho el método then devuelve otra promise cuyo estado depende del valor devuelto por la función que se pasa al then. Así podemos tener el siguiente código:
task1(). then(task2). then(task3). then(function() { console.log('Todas las Promises resueltas.'); });
Observa la elegancia de este código: ya no hay thens dentro de thens, ni nada parecido. En su lugar el código es muy claro y descriptivo. Puedes leerlo de la siguiente manera: ejecutar task1 y luego task2 y luego task3 y cuando task3 haya terminado ejecutas el último then.
¡Más claro y sencillo, no puede ser!
Este código funciona porque cada método then devuelve una promise, por esto los podemos encadenar.
Pero te estarás preguntando: ¿cuándo se resuelve la promise que devuelve el método then?. Pues la respuesta es muy simple:
then devuelva una promise, entonces cuando se resuelva esta última se resolverá también la promise devuelta por el método then. A todos los efectos es como si then retornase la misma promise que retorna la función que se le pasa como parámetro. A este hecho se le conoce como promise unwrapping.then no devuelva una promise entonces la promise devuelta por then estará en estado resuelta.then lance un error, entonces la promise devuelta por then estará en estado de error.
Volvamos a ver el código que encadena los thens pero ahora con comentarios para entender mejor qué ocurre a cada paso:
task1(). // En este punto tenemos una promise, llamémosla 'pr1' then(task2). // llamamos a then de pr1. Esto ejecutará pr2. // task2 devuelve una promise, llamémosla pr2. // En este caso el método then devuelve otra promise cuyo estado depende // del estado de pr2. Es como si el método then devolviese la misma pr2. then(task3). // Por lo tanto esta línea equivale a pr2.then(...). Por lo que este then // se ejecutará cuando pr2 se haya resuelto. // Igualmente task3 devuelve una promise (llamémosla pr3), // por lo que el método then(task3) devuelve una promise // cuyo estado depende de pr3 then(function() { // Este then pues se ejecutará cuando pr3 se haya resuelto. console.log('Todas las Promises resueltas.'); });
Vamos a ver algunos ejemplos del encadenamiento de métodos then que hemos comentado. Para cada ejemplo hay una captura de pantalla de la consola de Firefox para que veas el estado de los objetos.
El primer ejemplo muestra como then devuelve siempre una promise:
var pr = new Promise(function(r,j) { r(); }); var p1 = pr.then(function() { console.log('promise resolved'); });
Observa como p1 es una promise a pesar de que la función pasada por then no devuelve nada. Por ello la promise p1 está resuelta y su valor asociado es undefined
En este segundo ejemplo se puede observar como la promise devuelta por then está en estado de error si la función que se pasa al then lanza una excepción:
var pr = new Promise(function(r,j) { r(); }); var p2 = pr.then(function() { throw("some error!"); });
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:
var pr = new Promise(function(r,j) { r(); }); var p1 = pr.then(function() { return new Promise(function(r,j) { window.setTimeout(function() { console.log("promise inside then is resolved"); r(); }, 5000); }); }); var p2=p1.then(function() { console.log("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”:
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.
Vamos a ver cómo crear nuestras propias funciones que utilicen y devuelvan promises. Para ello debemos ver cómo crear objetos del tipo Promise.
La función constructora Promise acepta un solo parámetro, que se conoce como la “función ejecutora”. Dicha función contiene el código de la tarea que la promise encapsula:
var p = new Promise(function() { console.log('Hello promises'); });
El código que se coloca dentro de la función ejecutora se ejecuta inmediatamente. Es decir, el código anterior imprime la cadena “Hello promises” por la consola. El objeto p es la promise. En este caso este código sirve de poco porque la promise p está en estado pendiente, y no hay manera de resolverla (pasarla a estado completado) o rechazarla (pasarla a estado de error).
El resolver o rechazar una promise debe hacerse desde dentro de la función ejecutora.
Dicha función recibe dos parámetros (por regla general llamados resolve y reject) que son sendas funciones que al llamarlas nos permiten resolver o rechazar la promise. P. ej. el siguiente código devuelve una promise completada:
var p = new Promise(function(resolve, reject) { console.log('Hello promises'); resolve(); });
En este punto, la variable p es una promise en estado completado. Así podríamos tener una función foo que nos permitiese obtener promises (ya completadas):
var foo = function() { var p = new Promise(function(resolve, reject) { console.log('Hello promises'); resolve(); }); return p; };
Y ahora podríamos tener el siguiente código:
foo().then(function() { console.log('Hello after promises')});
Esto imprimiría por la consola primero la cadena “Hello promises” y luego la cadena “Hello after promises”. Fíjate en que foo devuelve la promise (ya completada), por lo que la siguiente llamada a then se ejecuta inmediatamente. Por supuesto este ejemplo es muy sencillo y usar promises en este escenario no aporta nada. Lo interesante viene cuando la función foo espera un callback, por la razón que sea. Los callbacks suelen usarse cuando hay operaciones asíncronas aunque no siempre tiene porque ser así.
Vamos a ver un ejemplo más interesante. En este caso encapsularemos la API getCurrentPosition para usar promises en lugar de callbacks. Primero definimos nuestra nueva función, que llamaremos getCurrentPositionPromise. Esta función llamará a getCurrentPosition y le pasará un callback. Dentro de éste resolveremos la promise. Parece complicado así explicado pero es muy simple:
navigator.geolocation.getCurrentPositionPromise = function() { var pr = new Promise(function(resolve, reject) { navigator.geolocation.getCurrentPosition(function(c) { resolve(c); // Resolvemos la promise }); }); return pr; // Devolvemos la promise }
La función getCurrentPositionPromise envuelve a la función nativa getCurrentPosition usando promises en lugar de callbacks. Por lo tanto ahora podemos tener el siguiente código:
var task = navigator.geolocation.getCurrentPositionPromise(); task.then(function(c) { console.log("Done -> ",c); });
Ejercicio: Completa la función getCurrentPositionPromise para que tenga presente que la función nativa getCurrentPosition puede dar un error y en este caso que pase la promise al estado de error (llamando a reject). En el fichero setInterval. Tienes la solución en el fichero encapsular2.js.
Fichero encapsular2.js:
var setIntervalPromises = function(interval) { var pr = new Promise(function(resolve, reject) { window.setInterval(function() { resolve(); }, interval); }); return pr; // Devolvemos la promise } // uso: // var wait = setIntervalPromises(interval) // wait.then(function() { ... });
Partimos de lo siguiente:
var demo = function(res, cb) { window.setTimeout(function() { cb(res); }, 2000 ); }
Para invocarla:
demo(10, function(r) { console.log("Recibido ->", r); });
Vamos a transformar esa función para utilizar una promise y así no tener que usar callbacks.
var demo = function(res) { var pr = new Promise(function(resolve, reject) { window.setTimeout(function() { resolve(res); }, 2000 ); }); return pr; }
Para utilizarla:
var task = demo(10); task.then(function(r) { console.log("Recibido ->", r); })
El resultado será el mismo que antes, pero ya no hay ninguna función de callback y queda más separado la llamada de la función de lo que ocurre cuando la función finaliza.
Veamos cómo podemos hacer una secuencia de promises y se ejecuten una tras otra.
Partimos de la siguiente función que esperaba un tiempo y luego resuelve la promise con un resultado.
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; }
Siempre que queramos encadenar tareas, hay que definir cada una de esas tareas en una función.
Primera tarea:
var task1 = function() { console.log("Iniciando tarea 1"); return demo(100); }
Segunda tarea:
var task2 = function() { console.log("Iniciando tarea 2"); return demo(2000); }
Para encadenar, primero llamamos a la primera tarea y luego, como la primera tarea devuelve una promise, cuando termine esta, llamaremos a la segunda tarea:
console.log("Inicio"); task1().then(task2).then(function() { console.log("Fin de todas las tareas"); });
En el apartado anterior hemos visto cómo encadenar las promises, pero no hemos hecho nada con los resultados. Cada una devolvía un resultado. Veamos cómo podemos tratarlos.
Primero tenemos que ver cómo la tarea 2 puede saber cuál es el resultado de la tarea 1. Basta con que lo reciba como parámetro:
var task2 = function(rp) { console.log("El resultado previo es ->" , rp); console.log("Iniciando tarea 2"); return demo(2000); }
Para tratar el resultado desde la propia tarea:
var task1 = function() { console.log("Iniciando tarea 1"); return demo(100).then(function(r) { console.log("En task1() el resultado es ", r); }); }
Sin embargo, perdemos el resultado en task2 por haber usado el then en task1. Modificamos task1 para devolver el resultado:
var task1 = function() { console.log("Iniciando tarea 1"); return demo(100).then(function(r) { console.log("En task1() el resultado es", r); return r; }); }
Todo el código junto:
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); } // Uso: console.log("Inicio"); task1().then(task2).then(function() { console.log("Fin de todas las tareas"); });
Una idea interesante de las promises es que pueden crearse promises compuestas, que solo se resuelven cuando todas las promises que las componen se resuelven a su vez (o que entran en el estado de error, en cuanto una de las promises que las componen entra en estado de error).
Para esta lección, se supone la existencia de una función llamada xhrget a la que se le pasa un parámetro (una cadena con URL) y usando XMLHttpRequest realiza una llamada AJAX GET a la URL indicada y devuelve una promise que se resuelve tan pronto como la petición AJAX está realizada. Se propone como ejercicio el realizar dicha función. En el fichero then puedes conseguir lo propuesto, con la salvedad de que estás forzando a que las 3 peticiones se ejecuten una tras otra de forma secuencial, cuando no hay necesidad de ello. En estos casos es cuando las promises compuestas son extremadamente útiles. Y, si nunca has tenido que resolver escenarios similares sin promises, usando variables contadoras y demás, apreciarás todavía más la simplicidad de las promises compuestas.
Para crear una promise compuesta, se usa el método Promise.all. A este método se le pasa un “iterable” (p. ej. un array) de promises y nos devuelve la promise compuesta. Tan simple como:
var p1 = xhrget('http://www.campusmvp.es/'); var p2 = xhrget('http://www.campusmvp.es/catalogo/'); var p3 = xhrget('http://www.campusmvp.es/opiniones-campusmvp.htm'); var pall = Promise.all([p1,p2,p3]); pall.then(function() { // En este punto las TRES promises (p1, p2 y p3) están resueltas. });
El concepto de “iterable” es nuevo en ECMAScript 2015. Básicamente por ahora podemos considerar que los iterables son arrays, aunque hay otros tipos que también lo son y más adelante veremos cómo hacer que nuestros propios objetos se comporten como iterables si así lo deseamos.
Por supuesto queda por aclarar un punto: cómo obtenemos los valores asociados a las distintas promises en el then de la promise compuesta.
El método then de una promise compuesta recibe como parámetro un array que contiene todos los resultados de las promises que la componen.
En nuestro caso dado que la promise devuelta por la función xhrget tiene como valor el propio objeto XMLHttpRequest usado, el método then de la promise compuesta recibe como parámetro un array de 3 elementos XMLHttpRequest.
El siguiente código ilustra con más claridad este concepto:
var p1 = new Promise(function(r,j) { r('one');}); // resolvemos con 'one' var p2 = new Promise(function(r,j) { r(42);}); // resolvemos con 42 var p3 = new Promise(function(r,j) { r({foo:'bar'});}); // resolvemos con {foo:'bar'} Promise.all([p1,p2,p3]).then(function(values) { console.log(values)}); // ['one', 42, {foo:'bar'}]
Veremos cómo podemos ejecutar promises todas a la vez, de forma paralela.
Partimos de:
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); }
console.log("Inicio"); Promise.all([task1(), task2()]).then(function () { console.log("Fin", arguments[0]); });
arguments[0] es un array con los resultados de todas las promises que forman la promise paralela.
Esto va muy bien para sincronizar peticiones AJAX. Este método nos ahorra utilizar contadores o variables globales.
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:
var errored = new Promise(function(resolve, reject) { reject(new Error("Some error hapened")); }); console.log(errored);
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:
errored.catch(function(err) { console.log(err); });
El método catch comparte todas las otras características del método then:
catch sobre una promise que YA está en estado de error, el método se ejecuta al momento.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).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).Vamos a forzar un error, es decir, que se devuelva una promise en estado de error.
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; }
Creamos la tarea y capturamos el error:
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");
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:
const TimeoutPromise = (pr, timeout) => Promise.race([pr, new Promise((_, rej) => setTimeout(rej, timeout) )]);
Su uso es muy sencillo:
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'));
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 el TC39 está trabajando en ello.
Existen 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:
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'; } } }
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:
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));
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).