javascript promise ecmascript-6

javascript - ¿Cómo saber cuándo se resuelven todas las promesas en un parámetro dinámico "iterable"?



promise ecmascript-6 (4)

Mi problema es que no sé cómo saber cuándo una matriz dinámica de promesas tiene todas las promesas resueltas.

Aquí un ejemplo:

var promiseArray = []; promiseArray.push(new Promise(){/*blablabla*/}); promiseArray.push(new Promise(){/*blablabla*/}); Promise.all(promiseArray).then(function(){ // This will be executen when those 2 promises are solved. }); promiseArray.push(new Promise(){/*blablabla*/});

Tengo un problema aqui. El comportamiento Promise.all se ejecutará cuando se resuelvan las 2 promesas anteriores, PERO, antes de que se resuelvan esas 2 promesas, se agregó una tercera promesa y esta nueva no se tendrá en cuenta.

Entonces, lo que necesito es decir algo como: "Hola Promise.all . Promise.all , tienen una matriz dinámica para verificar". ¿Cómo puedo hacerlo?

Recuerda que esto es solo un ejemplo. Sé que puedo mover la línea Promise.all a la última línea, pero en realidad las nuevas promesas se agregan dinámicamente cuando se resuelven otras promesas, y las nuevas promesas también podrían agregar nuevas promesas, por lo que es una matriz realmente dinámica.

El caso de uso real que tengo es algo como esto:

  1. Utilizo la API de Twitter para verificar si hay nuevos Tweets (usando la API de búsqueda).
  2. En caso de que encuentre nuevos Tweets, lo agrego a un MongoDB (aquí tenemos Promesas).
  3. En caso de que esos nuevos Tweets estén relacionados con un usuario que no tengo en mi MongoDB (aquí tenemos nuevas promesas porque tengo que ir a MongoDB para verificar si tengo ese usuario), vamos a Twitter API para obtener información del usuario (más promesa) y agregamos esos nuevos usuarios a MongoDB (sí, más promesas).
  4. Luego, voy a MongoDB para insertar nuevos valores para asociar los nuevos tweets con esos nuevos usuarios (¡más promesas! ¡Wiii!).
  5. Cuando se resuelvan todas las consultas a MongoDB (todas las selecciones, actualizaciones, inserciones), cierre la conexión MongoDB.

Otro ejemplo difícil:

var allPromises = []; allPromises.push(new Promise(function(done, fail){ mongoDB.connect(function(error){ //Because mongoDB works with callbacks instead of promises if(error) fail(); else ajax.get(''/whatever'').then(function(){ if (somethingHappens) { allPromises.push(new Promise(function(done, fail){ //This promise never will be take in account // bla bla bla if (somethingHappens) { allPromises.push(new Promise(function(done, fail){ //This promise never will be take in account // bla bla bla })); } else { ajax.get(''/whatever/2'').then(function(){ if (somethingHappens) { allPromises.push(new Promise(function(done, fail){ //This promise never will be take in account // bla bla bla })); } }); } })); } else { ajax.get(''/whatever/2'').then(function(){ if (somethingHappens) { allPromises.push(new Promise(function(done, fail){ //This promise never will be take in account // bla bla bla if (somethingHappens) { allPromises.push(new Promise(function(done, fail){ //This promise never will be take in account // bla bla bla })); } else { ajax.get(''/whatever/2'').then(function(){ if (somethingHappens) { allPromises.push(new Promise(function(done, fail){ //This promise never will be take in account // bla bla bla })); } }); } })); } }); } }); }); })); Promise.all(allPromises).then(function(){ // Soooo, all work is done! mongodb.close()! });

Entonces, ahora, un ejemplo de belleza. Necesitamos llamar a la función showAllTheInformation cuando se llama a la última promesa (no sabemos cuál es la última). ¿Cómo lo haces?:

var name = ''anonimus''; var date = ''we do not know''; function userClikOnLogIn() { $http.get(''/login/user/password'').then(function(data){ if (data.logguedOk) { $http.get(''/checkIfIsAdmin'').then(function(data){ if (data.yesHeIsAnAdmin) { $http.get(''/getTheNameOfTheUser'').then(function(data){ if(data.userHasName) { $http.get(''/getCurrentDate'').then(function(data){ currentDate = data.theNewCurrentDate; }); } }); } }); } }); } function showAllTheInformation() { alert(''Hi '' + name + '' today is:'' + date); }

Aquí otro ejemplo con más contexto: https://jsfiddle.net/f0a1s79o/2/


@JeffBowman y @Bergi tienen la idea correcta: esperar recursivamente y contar promesas. Aquí está mi implementación en Coffeescript)

Promise = require ''bluebird'' class DynamicPromiseCollection promises = [] add:(p)-> promises.push p wait_for_all:-> # # Wait for all current promises, then check for new promises... # ...if there are new promises, then keep waiting ( recursively ). # # Resolve only when all promises are done, and there are no new promises. # make_promise = -> num_before = promises.length p = Promise.all(promises).then -> num_after = promises.length if num_after > num_before return make_promise() # recursive -- wait again else return true # all done now p = make_promise() return p # # let''s test this... # promises = new DynamicPromiseCollection() # # pretend to get remote data # get_remote_data = -> new Promise (resolve,reject)-> setTimeout -> resolve "data" ,500 # # get data, wait, then get more data... # promises.add get_remote_data().then (data)-> console.log "got " + data promises.add get_remote_data().then (data)-> console.log "got " + data # # this should wait for both data # promises.wait_for_all().then -> console.log "...and wait_for_all is done."


No hay manera de salir. Promise.all poner todas las promesas en la matriz antes de llamar a Promise.all en ella. En el ejemplo que presentó, eso es tan simple como mover la última línea hacia arriba.

En caso de que esté llenando asincrónicamente la matriz, debe obtener una promesa para esa matriz y usar .then(Promise.all.bind(Promise)) . Si no sabe cuándo deja de agregar promesas, esto es imposible de todos modos, ya que es posible que nunca se resuelvan en absoluto.

Con respecto a su "ejemplo de belleza", querrá aprender sobre la magia del encadenamiento . Como dije anteriormente en los comentarios, debe return una promesa de cada función en la que está haciendo algo asíncrono. De hecho, solo agregue el return faltante s:

function userClikOnLogIn() { return $http.get(''/login/user/password'').then(function(data){ // ^^^^^^ if (data.logguedOk) { return $http.get(''/checkIfIsAdmin'').then(function(data){ // ^^^^^^ if (data.yesHeIsAnAdmin) { return $http.get(''/getTheNameOfTheUser'').then(function(data){ // ^^^^^^ if(data.userHasName) { return $http.get(''/getCurrentDate'').then(function(data){ // ^^^^^^ currentDate = data.theNewCurrentDate; }); } }); } }); } }); } userClikOnLogIn().then(function showAllTheInformation() { // ^^^^^ now you can chain onto it! alert(''Hi '' + name + '' today is:'' + date); });

No hay una serie de promesas aquí que crezcan dinámicamente, es solo que cada función está devolviendo una promesa para el resultado (asíncrono) de las cosas que hace.


Puede hacer una pequeña función recursiva para envolver Promise.all para manejar las adiciones a la promesa original:

/** * Returns a Promise that resolves to an array of inputs, like Promise.all. * * If additional unresolved promises are added to the passed-in iterable or * array, the returned Promise will additionally wait for those, as long as * they are added before the final promise in the iterable can resolve. */ function iterablePromise(iterable) { return Promise.all(iterable).then(function(resolvedIterable) { if (iterable.length != resolvedIterable.length) { // The list of promises or values changed. Return a new Promise. // The original promise won''t resolve until the new one does. return iterablePromise(iterable); } // The list of promises or values stayed the same. // Return results immediately. return resolvedIterable; }); } /* Test harness below */ function timeoutPromise(string, timeoutMs) { console.log("Promise created: " + string + " - " + timeoutMs + "ms"); return new Promise(function(resolve, reject) { window.setTimeout(function() { console.log("Promise resolved: " + string + " - " + timeoutMs + "ms"); resolve(); }, timeoutMs); }); } var list = [timeoutPromise(''original'', 1000)]; timeoutPromise(''list adder'', 200).then(function() { list.push(timeoutPromise(''newly created promise'', 2000)); }); iterablePromise(list).then(function() { console.log("All done!"); });

Tenga en cuenta que esto solo cubre la suma, y ​​que todavía es un poco peligroso: debe asegurarse de que la orden de devolución de llamada sea tal que cualquier promesa en vuelo se agregue a la lista antes de que se pueda invocar la devolución de llamada de Promises.all .


Si puede instrumentar las promesas o su uso, y los problemas de alcance lo permiten, entonces creo que podría abordar el problema de manera más simple: ¿cuántas promesas están pendientes?

En otras palabras, no necesita hacer un seguimiento de todas las promesas, solo contarlas.

var outstanding = 0; var p1 = new Promise(){/*blablabla*/}; var p2 = new Promise(){/*blablabla*/}; ++outstanding; p1.then( (data) => { ... if (0 >= --outstanding) // All resolved! } // dynamic set of promises, so later we decide to add another: var p3 = new Promise(){/*blablabla*/}; ++outstanding; p3.then( ... ); // as above

Para mejorar lo anterior, envuélvalo todo en la meta promesa (equivalente a la que Promise devolvería para un conjunto estático de promesas) ...

// Create a promise that tracks completion of a dynamic set of instrumented promises. getCompletionP() { let rslv = null; const p = new Promise( function(resolve, reject) { rslv = resolve; } ); p.resolve = rslv; assert( p.resolve ); p.scheduled = 0; p.onSchedule = function() { ++this.scheduled; }; p.onComplete = function() { if (0 >= --this.scheduled) this.resolve(); }; return p; }

Ahora llame a cp.onSchedule () antes de cada llamada a then (), y cp.onComplete al final de cada then (), y cp se resolverá después de que todas sus promesas se completen. (También necesitaría manejar las declaraciones catch, por supuesto).

Esto se resolverá cuando todo el código programado a través de Promise.then se complete, mientras que la pregunta pide algo que se resolverá cuando se resuelvan todas las promesas. Eso podría lograrse agregando llamadas después de la declaración de resolución de las promesas, pero eso es imposible si se usan bibliotecas de terceros, y creo que serían funcionalmente iguales.

Esto no funcionará en todos los casos, pero como la respuesta aceptada es que no se puede hacer (conjunto dinámico de promesas), creo que esto podría ser útil aunque se volvió más complicado (desordenado) a medida que lo escribía.