w3schools promises promesas para nodejs anidadas javascript promise es6-promise

javascript - promises - Espere hasta que se completen todas las promesas de ES6, incluso las promesas rechazadas



promise typescript (16)

Digamos que tengo un conjunto de promesas que están haciendo solicitudes de red, de las cuales una fallará:

// http://does-not-exist will throw a TypeError var arr = [ fetch(''index.html''), fetch(''http://does-not-exist'') ] Promise.all(arr) .then(res => console.log(''success'', res)) .catch(err => console.log(''error'', err)) // This is executed

Digamos que quiero esperar hasta que todo esto haya terminado, independientemente de si uno ha fallado. Puede haber un error de red para un recurso sin el cual puedo vivir, pero que si puedo obtener, quiero antes de continuar. Quiero manejar fallas de red con gracia.

Dado que Promises.all no deja espacio para esto, ¿cuál es el patrón recomendado para manejar esto, sin usar una biblioteca de promesas?


Aquí está mi costumbre settledPromiseAll()

var err = [fetch(''index.html'').then((success) => { return Promise.resolve(success); }).catch((e) => { return Promise.resolve(e); }), fetch(''http://does-not-exist'').then((success) => { return Promise.resolve(success); }).catch((e) => { return Promise.resolve(e); })]; Promise.all(err) .then(function (res) { console.log(''success'', res) }) .catch(function (err) { console.log(''error'', err) }) //never executed

En comparación con Promise.all

  • Si se resuelven todas las promesas, funciona exactamente como la estándar.

  • Si se rechaza una o más promesas, devuelve la primera rechazada de manera muy similar a la estándar pero, a diferencia de esto, espera que todas las promesas se resuelvan / rechacen.

Para los valientes podríamos cambiar Promise.all() :

function synchronousCode() { function myFetch(url) { try { return window.fetch(url).data; } catch (e) { return {status: ''failed:''+e}; }; }; var arr=[ myFetch("https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"), myFetch("https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/NONEXISTANT.js"), myFetch("https://ajax.NONEXISTANT123.com/ajax/libs/jquery/2.0.0/NONEXISTANT.js") ]; console.log(''array is ready:'',arr[0].status,arr[1].status,arr[2].status); }; nsynjs.run(synchronousCode,{},function(){ console.log(''done''); });

CUIDADO En general, nunca cambiamos los elementos integrados, ya que podría romper otras bibliotecas JS no relacionadas o chocar con futuros cambios en los estándares JS.

Mi establecido settledPromiseall es compatible con versiones anteriores de Promise.all y extiende su funcionalidad.

Personas que están desarrollando estándares: ¿por qué no incluir esto en un nuevo estándar de Promise?


Claro, solo necesitas un reflect :

const reflect = p => p.then(v => ({v, status: "fulfilled" }), e => ({e, status: "rejected" })); reflect(promise).then((v => { console.log(v.status); });

O con ES5:

function reflect(promise){ return promise.then(function(v){ return {v:v, status: "fulfilled" }}, function(e){ return {e:e, status: "rejected" }}); } reflect(promise).then(function(v){ console.log(v.status); });

O en tu ejemplo:

var arr = [ fetch(''index.html''), fetch(''http://does-not-exist'') ] Promise.all(arr.map(reflect)).then(function(results){ var success = results.filter(x => x.status === "fulfilled"); });


Creo que lo siguiente ofrece un enfoque ligeramente diferente ... compare fn_fast_fail() con fn_slow_fail() ... aunque este último no falla como tal ... puede verificar si uno o ambos de b es una instancia de Error y throw ese Error si desea que llegue al bloque catch (por ejemplo, if (b instanceof Error) { throw b; } ). Ver el jsfiddle .

(function() { var stdAll = Promise.all; Promise.all = function(values, wait) { if(!wait) return stdAll.call(Promise, values); return settledPromiseAll(values); } })();


Esto debería ser coherente con cómo lo hace Q :

const [{v: value1, e: error1}, {v: value2, e: error2}] = Promise.all(reflect.. if (!error1) { console.log(value1); }


Hay una proposal para una función que puede lograr esto de forma nativa, en Javascript vainilla: Promise.allSettled . Actualmente se encuentra en la etapa 4, y es muy probable que llegue a la especificación oficial. Es muy similar a la función de reflect en esta otra respuesta . Aquí hay un ejemplo, de la página de propuesta. Antes, habrías tenido que hacer:

function reflect(promise) { return promise.then( (v) => { return { status: ''fulfilled'', value: v }; }, (error) => { return { status: ''rejected'', reason: error }; } ); } const promises = [ fetch(''index.html''), fetch(''https://does-not-exist/'') ]; const results = await Promise.all(promises.map(reflect)); const successfulPromises = results.filter(p => p.status === ''fulfilled'');

Usando Promise.allSettled en Promise.allSettled lugar, lo anterior será equivalente a:

const promises = [ fetch(''index.html''), fetch(''https://does-not-exist/'') ]; const results = await Promise.allSettled(promises); const successfulPromises = results.filter(p => p.status === ''fulfilled'');

Una vez que esto se convierta en parte de la especificación y los navegadores lo implementen, podrá usarlo en navegadores modernos sin ninguna biblioteca .

Se ha enviado en Chrome 76, por lo que en él, el siguiente fragmento debe ejecutarse sin problemas:

var err; Promise.all([ promiseOne().catch(function(error) { err = error;}), promiseTwo().catch(function(error) { err = error;}) ]).then(function() { if (err) { throw err; } });

Salida:

const fetch = (url) => { return node-fetch(url) .then(result => result.json()) .catch((e) => { return new Promise((resolve) => setTimeout(() => resolve(fetch(url)), timeout)); }); }; tasks = [fetch(url1), fetch(url2) ....]; Promise.all(tasks).then(......)

Mientras tanto, aquí hay un polyfill que cumple con las especificaciones.


He estado usando los siguientes códigos desde ES5.

const p1 = Promise.resolve("OK"); const p2 = Promise.reject(new Error(":-(")); const p3 = Promise.resolve(1000); promiseExecuteAll([p1, p2, p3]).then((data) => { data.forEach(value => console.log(`${ value.isSuccess ? ''Resolve'' : ''Reject'' } >> ${ value.data }`)); }); /* Output: Resolve >> OK Reject >> :-( Resolve >> 1000 */

La firma de uso es como Promise.all . La principal diferencia es que Promise.wait esperará a que todas las promesas terminen sus trabajos.


La respuesta de Benjamin Gruenbaum es, por supuesto, genial. Pero también puedo ver si el punto de vista de Nathan Hagen con el nivel de abstracción parece vago. Tener propiedades de objetos cortos como e & v tampoco ayuda, pero por supuesto eso podría cambiarse.

En Javascript hay un objeto de error estándar, llamado Error Idealmente, siempre arrojas una instancia / descendiente de esto. La ventaja es que puede hacer una instanceof Error , y sabe que algo es un error.

Entonces, usando esta idea, aquí está mi opinión sobre el problema.

Básicamente, detecte el error, si el error no es del tipo Error, envuelva el error dentro de un objeto Error. La matriz resultante tendrá valores resueltos u objetos de error que puede verificar.

La instancia de dentro de la captura es en caso de que use alguna biblioteca externa que tal vez reject("error") , en lugar de reject(new Error("error")) .

Por supuesto, podría tener promesas si resuelve un error, pero en ese caso lo más probable es que tenga sentido tratarlo como un error, como muestra el último ejemplo.

Otra ventaja de hacerlo es que la destrucción de la matriz se mantiene simple.

function PromiseAllCatch(promises) { return Promise.all(promises.map(async m => { try { return await m; } catch(e) { if (e instanceof Error) return e; return new Error(e); } })); } async function test() { const ret = await PromiseAllCatch([ (async () => "this is fine")(), (async () => {throw new Error("oops")})(), (async () => "this is ok")(), (async () => {throw "Still an error";})(), (async () => new Error("resolved Error"))(), ]); console.log(ret); console.log(ret.map(r => r instanceof Error ? "error" : "ok" ).join(" : ")); } test();

En vez de

var p1 = new Promise((resolve, reject) => { setTimeout(() => resolve(''p1_delayed_resolvement''), 2000); }); var p2 = new Promise((resolve, reject) => { reject(new Error(''p2_immediate_rejection'')); }); var fn_fast_fail = async function () { try { var [a, b] = await Promise.all([p1, p2]); console.log(a); // "p1_delayed_resolvement" console.log(b); // "Error: p2_immediate_rejection" } catch (err) { console.log(''ERROR:'', err); } } var fn_slow_fail = async function () { try { var [a, b] = await Promise.all([ p1.catch(error => { return error }), p2.catch(error => { return error }) ]); console.log(a); // "p1_delayed_resolvement" console.log(b); // "Error: p2_immediate_rejection" } catch (err) { // we don''t reach here unless you throw the error from the `try` block console.log(''ERROR:'', err); } } fn_fast_fail(); // fails immediately fn_slow_fail(); // waits for delayed promise to resolve

Podría argumentar que la verificación !error1 es más simple que una instancia de, pero también tiene que destruir ambos v & e .

const settledPromiseAll = function(promisesArray) { var savedError; const saveFirstError = function(error) { if (!savedError) savedError = error; }; const handleErrors = function(value) { return Promise.resolve(value).catch(saveFirstError); }; const allSettled = Promise.all(promisesArray.map(handleErrors)); return allSettled.then(function(resolvedPromises) { if (savedError) throw savedError; return resolvedPromises; }); };


La respuesta de Benjamin ofrece una gran abstracción para resolver este problema, pero esperaba una solución menos abstracta. La forma explícita de resolver este problema es simplemente llamar a .catch en las promesas internas y devolver el error de su devolución de llamada.

let a = new Promise((res, rej) => res(''Resolved!'')), b = new Promise((res, rej) => rej(''Rejected!'')), c = a.catch(e => { console.log(''"a" failed.''); return e; }), d = b.catch(e => { console.log(''"b" failed.''); return e; }); Promise.all([c, d]) .then(result => console.log(''Then'', result)) // Then ["Resolved!", "Rejected!"] .catch(err => console.log(''Catch'', err)); Promise.all([a.catch(e => e), b.catch(e => e)]) .then(result => console.log(''Then'', result)) // Then ["Resolved!", "Rejected!"] .catch(err => console.log(''Catch'', err));

Dando un paso más allá, podría escribir un controlador genérico de captura que se vea así:

const catchHandler = error => ({ payload: error, resolved: false });

entonces puedes hacer

> Promise.all([a, b].map(promise => promise.catch(catchHandler)) .then(results => console.log(results)) .catch(() => console.log(''Promise.all failed'')) < [ ''Resolved!'', { payload: Promise, resolved: false } ]

El problema con esto es que los valores capturados tendrán una interfaz diferente a los valores no capturados, por lo que para limpiar esto puede hacer algo como:

const successHandler = result => ({ payload: result, resolved: true });

Entonces ahora puedes hacer esto:

> Promise.all([a, b].map(result => result.then(successHandler).catch(catchHandler)) .then(results => console.log(results.filter(result => result.resolved)) .catch(() => console.log(''Promise.all failed'')) < [ ''Resolved!'' ]

Luego, para mantenerlo SECO, obtienes la respuesta de Benjamin:

const reflect = promise => promise .then(successHandler) .catch(catchHander)

donde ahora parece

> Promise.all([a, b].map(result => result.then(successHandler).catch(catchHandler)) .then(results => console.log(results.filter(result => result.resolved)) .catch(() => console.log(''Promise.all failed'')) < [ ''Resolved!'' ]

Los beneficios de la segunda solución son su abstracción y SECO. La desventaja es que tiene más código y debe recordar reflejar todas sus promesas para hacer que las cosas sean consistentes.

Caracterizaría mi solución como explícita y KISS, pero de hecho menos robusta. La interfaz no garantiza que sepa exactamente si la promesa tuvo éxito o no.

Por ejemplo, puede tener esto:

const a = Promise.resolve(new Error(''Not beaking, just bad'')); const b = Promise.reject(new Error(''This actually didnt work''));

Esto no quedará atrapado por una a.catch , así que

> Promise.all([a, b].map(promise => promise.catch(e => e)) .then(results => console.log(results)) < [ Error, Error ]

No hay forma de saber cuál fue fatal y cuál no. Si eso es importante, entonces querrá aplicar una interfaz que haga un seguimiento de si tuvo éxito o no (lo que reflect hace).

Si solo desea manejar los errores con gracia, puede tratar los errores como valores indefinidos:

> Promise.all([a.catch(() => undefined), b.catch(() => undefined)]) .then((results) => console.log(''Known values: '', results.filter(x => typeof x !== ''undefined''))) < [ ''Resolved!'' ]

En mi caso, no necesito saber el error o cómo falló, solo me importa si tengo el valor o no. Dejaré que la función que genera la promesa se preocupe por registrar el error específico.

const apiMethod = () => fetch() .catch(error => { console.log(error.message); throw error; });

De esa manera, el resto de la aplicación puede ignorar su error si lo desea, y tratarlo como un valor indefinido si lo desea.

Quiero que mis funciones de alto nivel fallen con seguridad y no me preocupe por los detalles de por qué fallaron sus dependencias, y también prefiero KISS a DRY cuando tengo que hacer esa compensación, que es la razón por la que opté por no usar reflect .


No sé qué biblioteca de promesas está utilizando, pero la mayoría tiene algo como allSettled .

Editar: Ok, ya que desea usar ES6 simple sin bibliotecas externas, no existe tal método.

En otras palabras: debe realizar un bucle de sus promesas manualmente y resolver una nueva promesa combinada tan pronto como se resuelvan todas las promesas.


Puede ejecutar su lógica secuencialmente a través del ejecutor síncrono nsynjs . Pausará cada promesa, esperará la resolución / rechazo y asignará el resultado de la resolución a data propiedad de data , o lanzará una excepción (para manejar que necesitará el bloque try / catch). Aquí hay un ejemplo:

Promise.wait = function(promiseQueue){ if( !Array.isArray(promiseQueue) ){ return Promise.reject(''Given parameter is not an array!''); } if( promiseQueue.length === 0 ){ return Promise.resolve([]); } return new Promise((resolve, reject) =>{ let _pQueue=[], _rQueue=[], _readyCount=false; promiseQueue.forEach((_promise, idx) =>{ // Create a status info object _rQueue.push({rejected:false, seq:idx, result:null}); _pQueue.push(Promise.resolve(_promise)); }); _pQueue.forEach((_promise, idx)=>{ let item = _rQueue[idx]; _promise.then( (result)=>{ item.resolved = true; item.result = result; }, (error)=>{ item.resolved = false; item.result = error; } ).then(()=>{ _readyCount++; if ( _rQueue.length === _readyCount ) { let result = true; _rQueue.forEach((item)=>{result=result&&item.resolved;}); (result?resolve:reject)(_rQueue); } }); }); }); };

public promiseExecuteAll(promisesList: Promise<any>[] = []): Promise<{ data: any, isSuccess: boolean }[]> { let promise: Promise<{ data: any, isSuccess: boolean }[]>; if (promisesList.length) { const result: { data: any, isSuccess: boolean }[] = []; let count: number = 0; promise = new Promise<{ data: any, isSuccess: boolean }[]>((resolve, reject) => { promisesList.forEach((currentPromise: Promise<any>, index: number) => { currentPromise.then( (data) => // Success { result[index] = { data, isSuccess: true }; if (promisesList.length <= ++count) { resolve(result); } }, (data) => // Error { result[index] = { data, isSuccess: false }; if (promisesList.length <= ++count) { resolve(result); } }); }); }); } else { promise = Promise.resolve([]); } return promise; }


Realmente me gusta la respuesta de Benjamin, y cómo él básicamente convierte todas las promesas en siempre resueltas, pero a veces con errores como resultado. :)
Aquí está mi intento de su solicitud en caso de que estuviera buscando alternativas. Este método simplemente trata los errores como resultados válidos y se codifica de manera similar a Promise.all .

Promise.settle = function(promises) { var results = []; var done = promises.length; return new Promise(function(resolve) { function tryResolve(i, v) { results[i] = v; done = done - 1; if (done == 0) resolve(results); } for (var i=0; i<promises.length; i++) promises[i].then(tryResolve.bind(null, i), tryResolve.bind(null, i)); if (done == 0) resolve(results); }); }


Respuesta similar, pero más idiomática para ES6 quizás:

const a = Promise.resolve(1); const b = Promise.reject(new Error(2)); const c = Promise.resolve(3); Promise.all([a, b, c].map(p => p.catch(e => e))) .then(results => console.log(results)) // 1,Error: 2,3 .catch(e => console.log(e)); const console = { log: msg => div.innerHTML += msg + "<br>"};

<div id="div"></div>

Dependiendo del tipo (s) de valores devueltos, los errores a menudo se pueden distinguir con bastante facilidad (por ejemplo, use undefined para "no me importa", typeof para valores simples que no sean objetos, result.message , result.toString().startsWith("Error:") etc.)


Sé que esta pregunta tiene muchas respuestas, y estoy seguro de que deben (si no todas) son correctas. Sin embargo, fue muy difícil para mí entender la lógica / flujo de estas respuestas.

Así que miré la Implementación original en Promise.all() , e intenté imitar esa lógica, con la excepción de no detener la ejecución si fallaba una Promesa.

public promiseExecuteAll(promisesList: Promise<any>[] = []): Promise<{ data: any, isSuccess: boolean }[]> { let promise: Promise<{ data: any, isSuccess: boolean }[]>; if (promisesList.length) { const result: { data: any, isSuccess: boolean }[] = []; let count: number = 0; promise = new Promise<{ data: any, isSuccess: boolean }[]>((resolve, reject) => { promisesList.forEach((currentPromise: Promise<any>, index: number) => { currentPromise.then( (data) => // Success { result[index] = { data, isSuccess: true }; if (promisesList.length <= ++count) { resolve(result); } }, (data) => // Error { result[index] = { data, isSuccess: false }; if (promisesList.length <= ++count) { resolve(result); } }); }); }); } else { promise = Promise.resolve([]); } return promise; }

Explicación:
- Pase por la lista de promisesList entrada y ejecute cada promesa.
- No importa si la Promesa se resolvió o rechazó: guarde el resultado de la Promesa en una matriz de result acuerdo con el index . Guarde también el estado de resolución / rechazo ( isSuccess ).
- Una vez completadas todas las promesas, devuelve una promesa con el resultado de todas las demás.

Ejemplo de uso:

const p1 = Promise.resolve("OK"); const p2 = Promise.reject(new Error(":-(")); const p3 = Promise.resolve(1000); promiseExecuteAll([p1, p2, p3]).then((data) => { data.forEach(value => console.log(`${ value.isSuccess ? ''Resolve'' : ''Reject'' } >> ${ value.data }`)); }); /* Output: Resolve >> OK Reject >> :-( Resolve >> 1000 */


Tuve el mismo problema y lo resolví de la siguiente manera:

const [value1, value2] = PromiseAllCatch(promises); if (!(value1 instanceof Error)) console.log(value1);

En ese caso, Promise.all esperará a que cada Promesa entre en estado resolved o rejected .

Y teniendo esta solución, estamos "deteniendo la ejecución de catch " de una manera no bloqueante. De hecho, no detenemos nada, solo devolvemos la Promise en un estado pendiente que devuelve otra Promise cuando se resuelve después del tiempo de espera.


Yo lo haría:

<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>


if(!Promise.allSettled) { Promise.allSettled = function (promises) { return Promise.all(promises.map(p => Promise.resolve(p).then(v => ({ state: ''fulfilled'', value: v, }), r => ({ state: ''rejected'', reason: r, })))); }; }

Promise.all se tragará cualquier promesa rechazada y almacenará el error en una variable, por lo que volverá cuando se hayan resuelto todas las promesas. Luego puede volver a tirar el error o hacer lo que sea. De esta manera, supongo que sacarías el último rechazo en lugar del primero.