javascript - promises - Manejo de errores en Promise.all
promises javascript w3schools (13)
¿Has considerado
Promise.prototype.finally()
?
Parece estar diseñado para hacer exactamente lo que desea: ejecutar una función una vez que todas las promesas se hayan resuelto (resuelto / rechazado), independientemente de que algunas de las promesas sean rechazadas.
De la documentación de MDN :
El método
finally()
puede ser útil si desea realizar algún procesamiento o limpieza una vez que se cumpla la promesa, independientemente de su resultado.
El método
finally()
es muy similar a llamar a
.then(onFinally, onFinally)
sin embargo, hay un par de diferencias:
Al crear una función en línea, puede pasarla una vez, en lugar de verse obligado a declararla dos veces o crear una variable para ella.
Una devolución de llamada finalmente no recibirá ningún argumento, ya que no hay medios confiables para determinar si la promesa se cumplió o rechazó. Este caso de uso es precisamente cuando no le importa el motivo del rechazo o el valor de cumplimiento, por lo que no es necesario proporcionarlo.
A diferencia de
Promise.resolve(2).then(() => {}, () => {})
(que se resolverá con undefined),
Promise.resolve(2).finally(() => {})
se resolverá con 2. Del mismo modo, a diferencia de
Promise.reject(3).then(() => {}, () => {})
(que se cumplirá con undefined),
Promise.reject(3).finally(() => {})
será rechazado con 3.
== Fallback ==
Si su versión de JavaScript no es compatible con
Promise.prototype.finally()
, puede usar esta solución alternativa de
Jake Archibald
:
Promise.all(promises.map(p => p.catch(() => undefined)));
Tengo una serie de Promesas que estoy resolviendo con Promise.all (arrayOfPromises);
Continúo para continuar la cadena de promesa. Se ve algo como esto
existingPromiseChain = existingPromiseChain.then(function() {
var arrayOfPromises = state.routes.map(function(route){
return route.handler.promiseHandler();
});
return Promise.all(arrayOfPromises)
});
existingPromiseChain = existingPromiseChain.then(function(arrayResolved) {
// do stuff with my array of resolved promises, eventually ending with a res.send();
});
Quiero agregar una declaración catch para manejar una promesa individual en caso de que se produzca un error, pero cuando lo intento, Promise.all devuelve el primer error que encuentra (ignora el resto), y luego no puedo obtener los datos del resto de Las promesas en la matriz (que no error).
He intentado hacer algo como ...
existingPromiseChain = existingPromiseChain.then(function() {
var arrayOfPromises = state.routes.map(function(route){
return route.handler.promiseHandler()
.then(function(data) {
return data;
})
.catch(function(err) {
return err
});
});
return Promise.all(arrayOfPromises)
});
existingPromiseChain = existingPromiseChain.then(function(arrayResolved) {
// do stuff with my array of resolved promises, eventually ending with a res.send();
});
Pero eso no se resuelve.
¡Gracias!
-
Editar:
Lo que dijeron las respuestas a continuación era completamente cierto, el código se estaba rompiendo debido a otras razones. En caso de que alguien esté interesado, esta es la solución con la que terminé ...
Cadena de servidor Express Node
serverSidePromiseChain
.then(function(AppRouter) {
var arrayOfPromises = state.routes.map(function(route) {
return route.async();
});
Promise.all(arrayOfPromises)
.catch(function(err) {
// log that I have an error, return the entire array;
console.log(''A promise failed to resolve'', err);
return arrayOfPromises;
})
.then(function(arrayOfPromises) {
// full array of resolved promises;
})
};
Llamada API (llamada route.async)
return async()
.then(function(result) {
// dispatch a success
return result;
})
.catch(function(err) {
// dispatch a failure and throw error
throw err;
});
Poner el .catch para Promise.all antes del .then parece haber servido para atrapar cualquier error de las promesas originales, pero luego devolver toda la matriz al siguiente .then
¡Gracias!
Alternativamente, si tiene un caso en el que no le importan especialmente los valores de las promesas resueltas cuando hay un fallo pero aún desea que se ejecuten, puede hacer algo como esto que se resolverá con las promesas de manera normal cuando todos tienen éxito y rechazan con las promesas fallidas cuando alguno de ellos falla:
function promiseNoReallyAll (promises) {
return new Promise(
async (resolve, reject) => {
const failedPromises = []
const successfulPromises = await Promise.all(
promises.map(
promise => promise.catch(error => {
failedPromises.push(error)
})
)
)
if (failedPromises.length) {
reject(failedPromises)
} else {
resolve(successfulPromises)
}
}
)
}
Así es como
Promise.all
está diseñado para funcionar.
Si una sola promesa
reject()
, todo el método falla inmediatamente.
Hay casos de uso en los que uno podría desear tener
Promise.all
permitiendo que las promesas fracasen.
Para que esto suceda, simplemente no use ninguna declaración de
reject()
en su promesa.
Sin embargo, para garantizar que su aplicación / script no se congele en caso de que alguna promesa subyacente
nunca
reciba una respuesta, debe poner un tiempo de espera.
function getThing(uid,branch){
return new Promise(function (resolve, reject) {
xhr.get().then(function(res) {
if (res) {
resolve(res);
}
else {
resolve(null);
}
setTimeout(function(){reject(''timeout'')},10000)
}).catch(function(error) {
resolve(null);
});
});
}
Como @jib dijo:
Promise.all
es todo o nada.
Sin embargo, puede controlar ciertas promesas que están "permitidas" a fallar y nos gustaría proceder a
.then
.
Por ejemplo.
Promise.all([
doMustAsyncTask1,
doMustAsyncTask2,
doOptionalAsyncTask
.catch(err => {
if( /* err non-critical */) {
return
}
// if critical then fail
throw err
})
])
.then(([ mustRes1, mustRes2, optionalRes ]) => {
// proceed to work with results
})
Escribí una biblioteca npm para lidiar con este problema más hermoso. https://github.com/wenshin/promiseallend
Instalar
npm i --save promiseallend
2017-02-25 nueva api, no es romper los principios de promesa
const promiseAllEnd = require(''promiseallend'');
const promises = [Promise.resolve(1), Promise.reject(''error''), Promise.resolve(2)];
const promisesObj = {k1: Promise.resolve(1), k2: Promise.reject(''error''), k3: Promise.resolve(2)};
// input promises with array
promiseAllEnd(promises, {
unhandledRejection(error, index) {
// error is the original error which is ''error''.
// index is the index of array, it''s a number.
console.log(error, index);
}
})
// will call, data is `[1, undefined, 2]`
.then(data => console.log(data))
// won''t call
.catch(error => console.log(error.detail))
// input promises with object
promiseAllEnd(promisesObj, {
unhandledRejection(error, prop) {
// error is the original error.
// key is the property of object.
console.log(error, prop);
}
})
// will call, data is `{k1: 1, k3: 2}`
.then(data => console.log(data))
// won''t call
.catch(error => console.log(error.detail))
// the same to `Promise.all`
promiseAllEnd(promises, {requireConfig: true})
// will call, `error.detail` is ''error'', `error.key` is number 1.
.catch(error => console.log(error.detail))
// requireConfig is Array
promiseAllEnd(promises, {requireConfig: [false, true, false]})
// won''t call
.then(data => console.log(data))
// will call, `error.detail` is ''error'', `error.key` is number 1.
.catch(error => console.log(error.detail))
// requireConfig is Array
promiseAllEnd(promises, {requireConfig: [true, false, false]})
// will call, data is `[1, undefined, 2]`.
.then(data => console.log(data))
// won''t call
.catch(error => console.log(error.detail))
————————————————————————————————
Antigua mala API, ¡no la uses!
let promiseAllEnd = require(''promiseallend'');
// input promises with array
promiseAllEnd([Promise.resolve(1), Promise.reject(''error''), Promise.resolve(2)])
.then(data => console.log(data)) // [1, undefined, 2]
.catch(error => console.log(error.errorsByKey)) // {1: ''error''}
// input promises with object
promiseAllEnd({k1: Promise.resolve(1), k2: Promise.reject(''error''), k3: Promise.resolve(2)})
.then(data => console.log(data)) // {k1: 1, k3: 2}
.catch(error => console.log(error.errorsByKey)) // {k2: ''error''}
Para aquellos que usan ES8 que tropiezan aquí, pueden hacer algo como lo siguiente, utilizando funciones asíncronas :
var arrayOfPromises = state.routes.map(async function(route){
try {
return await route.handler.promiseHandler();
} catch(e) {
// Do something to handle the error.
// Errored promises will return whatever you return here (undefined if you don''t return anything).
}
});
var resolvedPromises = await Promise.all(arrayOfPromises);
Para continuar con el bucle
Promise.all
(incluso cuando una Promesa rechaza) escribí una función de utilidad que se llama
executeAllPromises
.
Esta función de utilidad devuelve un objeto con
results
y
errors
.
La idea es que todas las promesas que pase para
executeAllPromises
incluirán en una nueva promesa que siempre se resolverá.
La nueva promesa se resuelve con una matriz que tiene 2 puntos.
El primer punto contiene el valor de resolución (si lo hay) y el segundo lugar mantiene el error (si la promesa envuelta lo rechaza).
Como paso final,
executeAllPromises
acumula todos los valores de las promesas
executeAllPromises
y devuelve el objeto final con una matriz para
results
y una matriz para
errors
.
Aquí está el código:
function executeAllPromises(promises) {
// Wrap all Promises in a Promise that will always "resolve"
var resolvingPromises = promises.map(function(promise) {
return new Promise(function(resolve) {
var payload = new Array(2);
promise.then(function(result) {
payload[0] = result;
})
.catch(function(error) {
payload[1] = error;
})
.then(function() {
/*
* The wrapped Promise returns an array:
* The first position in the array holds the result (if any)
* The second position in the array holds the error (if any)
*/
resolve(payload);
});
});
});
var errors = [];
var results = [];
// Execute all wrapped Promises
return Promise.all(resolvingPromises)
.then(function(items) {
items.forEach(function(payload) {
if (payload[1]) {
errors.push(payload[1]);
} else {
results.push(payload[0]);
}
});
return {
errors: errors,
results: results
};
});
}
var myPromises = [
Promise.resolve(1),
Promise.resolve(2),
Promise.reject(new Error(''3'')),
Promise.resolve(4),
Promise.reject(new Error(''5''))
];
executeAllPromises(myPromises).then(function(items) {
// Result
var errors = items.errors.map(function(error) {
return error.message
}).join('','');
var results = items.results.join('','');
console.log(`Executed all ${myPromises.length} Promises:`);
console.log(`— ${items.results.length} Promises were successful: ${results}`);
console.log(`— ${items.errors.length} Promises failed: ${errors}`);
});
Podemos manejar el rechazo a nivel de promesas individuales, de modo que cuando obtengamos los resultados en nuestra matriz de resultados, el índice de la matriz que ha sido rechazado no estará
undefined
.
Podemos manejar esa situación según sea necesario y usar los resultados restantes.
Aquí he rechazado la primera promesa, por lo que es indefinida, pero podemos usar el resultado de la segunda promesa, que se encuentra en el índice 1.
const manyPromises = Promise.all([func1(), func2()]).then(result => {
console.log(result[0]); // undefined
console.log(result[1]); // func2
});
function func1() {
return new Promise( (res, rej) => rej(''func1'')).catch(err => {
console.log(''error handled'', err);
});
}
function func2() {
return new Promise( (res, rej) => setTimeout(() => res(''func2''), 500) );
}
Siempre puede ajustar sus funciones de devolución de promesas de manera que detecten el error y devuelvan en su lugar un valor acordado (por ejemplo, error.message), de modo que la excepción no llegue hasta la función Promise.all y la desactive.
async function resetCache(ip) {
try {
const response = await axios.get(`http://${ip}/resetcache`);
return response;
}catch (e) {
return {status: ''failure'', reason: ''e.message''};
}
}
Usando Async aguarde -
aquí una función asíncrona func1 devuelve un valor resuelto, y func2 arroja un error y devuelve un valor nulo en esta situación, podemos manejarlo como queramos y devolverlo en consecuencia.
const callingFunction = async () => {
const manyPromises = await Promise.all([func1(), func2()]);
console.log(manyPromises);
}
const func1 = async () => {
return ''func1''
}
const func2 = async () => {
try {
let x;
if (!x) throw "x value not present"
} catch(err) {
return null
}
}
callingFunction();
La salida es - [''func1'', null]
si puede usar la biblioteca q https://github.com/kriskowal/q tiene el método q.allSettled () que puede resolver este problema, puede manejar todas las promesas dependiendo de su estado, ya sea completo o rechazado.
existingPromiseChain = existingPromiseChain.then(function() {
var arrayOfPromises = state.routes.map(function(route){
return route.handler.promiseHandler();
});
return q.allSettled(arrayOfPromises)
});
existingPromiseChain = existingPromiseChain.then(function(arrayResolved) {
//so here you have all your promises the fulfilled and the rejected ones
// you can check the state of each promise
arrayResolved.forEach(function(item){
if(item.state === ''fulfilled''){ // ''rejected'' for rejected promises
//do somthing
} else {
// do something else
}
})
// do stuff with my array of resolved promises, eventually ending with a res.send();
});
Promise.all
es todo o nada.
Se resuelve una vez que todas las promesas en la matriz se resuelven o rechazan tan pronto como
una
de ellas rechaza.
En otras palabras, se resuelve con una matriz de todos los valores resueltos o se rechaza con un solo error.
Algunas bibliotecas tienen algo llamado
Promise.when
. Cuando entiendo, esperaría a que
todas las
promesas en la matriz se resuelvan o rechacen, pero no estoy familiarizado con eso, y no está en ES6.
Tu codigo
Estoy de acuerdo con otros aquí en que su solución debería funcionar. Debe resolverse con una matriz que puede contener una combinación de valores exitosos y objetos de error. Es inusual pasar objetos de error en la ruta de éxito, pero suponiendo que su código los esté esperando, no veo ningún problema.
La única razón por la que puedo pensar por qué "no se resolvería" es que está fallando en el código que no nos está mostrando y la razón por la que no está viendo ningún mensaje de error al respecto es porque esta cadena de promesa no finaliza con un final atrapar (en cuanto a lo que nos estás mostrando de todos modos).
Me tomé la libertad de descifrar la "cadena existente" de su ejemplo y terminar la cadena con una trampa. Puede que esto no sea adecuado para usted, pero para las personas que leen esto, es importante siempre devolver o terminar las cadenas, o los posibles errores, incluso los errores de codificación, se ocultarán (que es lo que sospecho que sucedió aquí):
Promise.all(state.routes.map(function(route) {
return route.handler.promiseHandler().catch(function(err) {
return err;
});
}))
.then(function(arrayOfValuesOrErrors) {
// handling of my array containing values and/or errors.
})
.catch(function(err) {
console.log(err.message); // some coding error in handling happened
});
NUEVA RESPUESTA
const results = await Promise.all(promises.map(p => p.catch(e => e)));
const validResults = results.filter(result => !(result instanceof Error));
API FUTURE Promise
- Chrome 76: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
ANTIGUA RESPUESTA
Tenemos que escribir Promise.all () personalizado. Aquí la solución que uso en mi proyecto. El error será devuelto como resultado normal. Después de que finalicen todas las promesas, podemos filtrar el error.
const Promise_all = promises => {
return new Promise((resolve, reject) => {
const results = [];
let count = 0;
promises.forEach((promise, idx) => {
promise
.catch(err => {
return err;
})
.then(valueOrError => {
results[idx] = valueOrError;
count += 1;
if (count === promises.length) resolve(results);
});
});
});
};
const results = await Promise_all(promises)
const validResults = results.filter(result => !(result instanceof Error));