Manejo de errores en Promise.all

267

Tengo una serie de promesas con las que estoy resolviendo 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.alldevuelve 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 falló).

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 .catchfor Promise.allantes del .thenparece haber servido para capturar cualquier error de las promesas originales, pero luego devolver toda la matriz al siguiente.then

¡Gracias!

Jon
fuente
2
Parece que su intento debería funcionar ... ¿tal vez hay otro problema en algún lugar más tarde?
Ry-
.then(function(data) { return data; })puede omitirse por completo
Bergi
La única razón por la que lo anterior no debería resolverse es si no nos está mostrando todo el código en los controladores theno catchhay un error que se arroja dentro. Por cierto, ¿es este nodo?
1
No tiene una captura final en su "cadena existente", por lo que puede haber errores que no está viendo que podrían explicar por qué "no se resuelve". Intente agregar eso y vea qué error obtiene.
foque
Aquí está la respuesta: stackoverflow.com/questions/31424561/…
Humoyun Ahmad

Respuestas:

191

Promise.allEs 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, que entiendo esperarí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í 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á 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
});
foque
fuente
44
Usted (y los comentarios anteriores) tenían razón. Mi route.handler.promiseHandler necesitaba .catch () y devolver el error. También necesitaba agregar el .catch final () al final de la cadena. Gracias por transmitir la importancia de tener controladores de éxito / error en cada paso de la cadena :).
Jon
2
También descubrí que si arrojo el error en mi .catch () para route.handler.promiseHandler, automáticamente irá a la captura final. Si en cambio devuelvo el error, hará lo que quiera y manejará toda la matriz.
Jon
2
Ahora hay un método estándar Promise.allSettled()con un soporte decente. Ver referencia .
Andréa Maugars
Sí, Promise.allfalla, cuando falla el primer hilo. Pero desafortunadamente, todos los otros subprocesos siguen funcionando hasta que terminan. Nada se cancela, peor aún: no hay forma de cancelar un hilo Promise. Entonces, independientemente de lo que estén haciendo (y manipulando) los subprocesos, continúan, cambian estados y variables, usan CPU, pero al final no devuelven su resultado. Debe tener en cuenta esto para no producir un caos, por ejemplo, cuando repite / reintenta la llamada.
Marc Wäckerlin
144

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

Solominh
fuente
11
Aunque eno tiene que ser un Error. Puede ser una cadena, por ejemplo, si alguien lo devuelve como Promise.reject('Service not available').
Klesun
@ArturKlesun, ¿cómo podríamos clasificar cuál de las promesas resultó en un error y cuál no?
Shubham Jain
55
@ shubham-jain con .then()y .catch(). Promise.resolve()pasaría valor al primero, mientras que Promise.reject()lo pasará al segundo. Se puede envolver en objeto, por ejemplo: p.then(v => ({success: true, value: v})).catch(e => ({success: false, error: e})).
Klesun
2
¿Por qué filtrarías los resultados? Eso no tiene sentido si está haciendo algo con los resultados: ¡necesita la orden para saber qué valor devuelto proviene de qué promesa!
Ryan Taylor
21

Para continuar el Promise.allciclo (incluso cuando una Promesa rechaza) escribí una función de utilidad que se llama executeAllPromises. Esta función de utilidad devuelve un objeto con resultsy errors.

La idea es que todas las promesas que pases se executeAllPromisesincluirá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, executeAllPromisesacumula todos los valores de las promesas envueltas y devuelve el objeto final con una matriz para resultsy 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}`);
});

Benny Neugebauer
fuente
2
Esto se puede hacer más simple. Ver stackoverflow.com/a/36115549/918910
jib
18

ES2020 presenta un nuevo método para el tipo de Promesa: Promise.allSettled()
Promise.allSettled le da una señal cuando se liquidan todas las promesas de entrada, lo que significa que se cumplen o se rechazan. Esto es útil en casos en los que no le importa el estado de la promesa, solo desea saber cuándo se realiza el trabajo, independientemente de si fue exitoso.

const promises = [
  fetch('/api-call-1'),
  fetch('/api-call-2'),
  fetch('/api-call-3'),
];
// Imagine some of these requests fail, and some succeed.

const result = await Promise.allSettled(promises);
console.log(result.map(x=>s.status));
// ['fulfilled', 'fulfilled', 'rejected']

Lea más en la publicación del blog v8 https://v8.dev/features/promise-combinators

MosheZada
fuente
13

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 continuar .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
  })
Germán
fuente
6

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();
});
Mohamed Mahmoud
fuente
Como sugiere el uso de alguna biblioteca ( q), sería más útil si proporcionara un ejemplo de uso relacionado con la pregunta. Tal como está, su respuesta no explica cómo esta biblioteca puede ayudar a resolver el problema.
ishmaelMakitla
agregó un ejemplo como se sugiere
Mohamed Mahmoud
1
Alrededor de 2018 siempre se debe ver lo que Sindre tiene disponible :-). github.com/sindresorhus/p-settle . Con los módulos de propósito único de Sindre, no tiene que importar una gran biblioteca como q por un solo bit.
DKebler
6

Usando Async aguarde -

aquí una función asíncrona func1 está devolviendo un valor resuelto, y func2 arroja un error y devuelve un valor nulo en esta situación, podemos manejarlo como queremos 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]

Nayan Patel
fuente
4

Para aquellos que usan ES8 que tropiezan aquí, pueden hacer algo como lo siguiente, usando 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);
Tyler Yasaka
fuente
3

Podemos manejar el rechazo a nivel de promesas individuales, por lo que cuando obtengamos los resultados en nuestra matriz de resultados, el índice de la matriz que ha sido rechazado será 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) );
}

Nayan Patel
fuente
¿Cómo puedes hacer algo similar si usamos async wait?
Rudresh Ajgaonkar
He respondido su pregunta, por favor encuentre el enlace para la respuesta. stackoverflow.com/a/55216763/4079716
Nayan Patel
2

¿Lo 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 finally()método puede ser útil si desea realizar algún procesamiento o limpieza una vez que se cumpla la promesa, independientemente de su resultado.

El finally()método es muy similar a llamar, .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 Promise.resolve(2).then(() => {}, () => {})(que se resolverá con undefined), Promise.resolve(2).finally(() => {})se resolverá con 2. De manera similar, a diferencia de Promise.reject(3).then(() => {}, () => {})(que se cumplirá con undefined), Promise.reject(3).finally(() => {})se rechazará con 3.

== Fallback ==

Si su versión de JavaScript no es compatible Promise.prototype.finally(), puede usar esta solución alternativa de Jake Archibald :Promise.all(promises.map(p => p.catch(() => undefined)));

Tom Auger
fuente
1
Sí, hasta que Promises.allSettled()se implemente realmente (está documentado por MDN aquí ), entonces Promises.all.finally()parecería lograr lo mismo. Estoy a punto de intentarlo ...
James
@jamess ¿Por qué no haces este comentario como una respuesta adecuada? Ninguna de las respuestas se refiere a ES6 allSettled().
pravin
@pravin: por lo que puedo decir, allSettled()no está implementado en ninguna parte (todavía), por lo que no quiero adelantarme a la realidad. Tuve éxito con Promises.all(myPromiseArray).finally()eso, y eso encaja con esta respuesta. Una vez que allSettled()realmente exista, entonces podría probarlo y descubrir cómo funciona realmente. Hasta entonces, ¿quién sabe qué implementarán realmente los navegadores? A menos que tenga información reciente de lo contrario ...
James
@jamess Es cierto que todavía está en la etapa de borrador ... sin embargo, el último FF y Chrome parecen soportarlo completamente ... No estoy seguro de su estabilidad aunque ... Mozilla Docs De todos modos, el punto que estaba tratando de hacer era que sería mucho más fácil de encontrar si fue una respuesta que un comentario ... aunque llames :)
pravin
@pravin: cuando publiqué mi comentario, no estaba implementado en ningún lado. Acabo de probar en Firefox y Chrome: Promise.allSettledno está implementado en Firefox, pero parece existir en Chrome. Solo porque los documentos dicen que está implementado no significa que realmente esté implementado. No lo voy a usar en el corto plazo.
James
0

Alternativamente, si tiene un caso en el que no le importan particularmente los valores de las promesas resueltas cuando hay una falla pero aún desea que se ejecuten, podría 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)
      }
    }
  )
}
Eric
fuente
0

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 a la función Promise.all y la deshabilite.

async function resetCache(ip) {

    try {

        const response = await axios.get(`http://${ip}/resetcache`);
        return response;

    }catch (e) {

        return {status: 'failure', reason: 'e.message'};
    }

}
Tamir Nakar
fuente
0

He encontrado una manera (solución) para hacer esto sin hacer que se sincronice.

Entonces, como se mencionó anteriormente, no Promise.alles nada.

entonces ... Use una promesa que lo encierra para atrapar y forzar la resolución.


      let safePromises = originalPrmises.map((imageObject) => {
            return new Promise((resolve) => {
              // Do something error friendly
              promise.then(_res => resolve(res)).catch(_err => resolve(err))
            })
        })
    })

    // safe
    return Promise.all(safePromises)
Juan Sebastián Contreras Aceve
fuente
0

Debería saber cómo identificar un error en sus resultados. Si no tiene un error esperado estándar, le sugiero que ejecute una transformación en cada error en el bloque catch que lo haga identificable en sus resultados.

try {
  let resArray = await Promise.all(
    state.routes.map(route => route.handler.promiseHandler().catch(e => e))
  );

  // in catch(e => e) you can transform your error to a type or object
  // that makes it easier for you to identify whats an error in resArray
  // e.g. if you expect your err objects to have e.type, you can filter
  // all errors in the array eg
  // let errResponse = resArray.filter(d => d && d.type === '<expected type>')
  // let notNullResponse = resArray.filter(d => d)

  } catch (err) {
    // code related errors
  }
Anthony Awuley
fuente
0

No es la mejor manera de registrar el error, pero siempre puede configurar todo en una matriz para el promiseAll y almacenar los resultados resultantes en nuevas variables.

Si usa GraphQL, debe procesar la respuesta independientemente y si no encuentra la referencia correcta, bloqueará la aplicación, reduciendo el problema.

const results = await Promise.all([
  this.props.client.query({
    query: GET_SPECIAL_DATES,
  }),
  this.props.client.query({
    query: GET_SPECIAL_DATE_TYPES,
  }),
  this.props.client.query({
    query: GET_ORDER_DATES,
  }),
]).catch(e=>console.log(e,"error"));
const specialDates = results[0].data.specialDates;
const specialDateTypes = results[1].data.specialDateTypes;
const orderDates = results[2].data.orders;
Vincent Tang
fuente
-1

Así es como Promise.allestá diseñado para funcionar. Si se trata de una sola promesa reject(), el método completo falla inmediatamente.

Hay casos de uso en los que uno podría desear que las Promise.allpromesas fracasen. Para que esto suceda, simplemente no use ninguna reject()declaración 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 ponerle 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);
        });
    });
}
Ronnie Royston
fuente
Aquí está la respuesta: stackoverflow.com/questions/31424561/…
Humoyun Ahmad
No usar reject()en su promesa está bien, pero ¿qué pasa si necesita usar las promesas de otra biblioteca?
Dan Dascalescu
-8

Escribí una biblioteca npm para lidiar con este problema más hermoso. https://github.com/wenshin/promiseallend

Instalar en pc

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'}
Wenshin
fuente
¿Como funciona? Muestre y explique su implementación de la función.
Bergi
Escribí una nueva lógica concurrente como Promise.all. Pero recopilará todos los datos y errores de cada promesa. También es compatible con la entrada de objetos, no es punto. Después de recopilar todos los datos y errores, anulo el promise.thenmétodo para tratar las devoluciones de llamada registradas que incluyen rechazado y cumplido. Para más detalles, puede ver el código
wenshin
¿Ese código llamará a ambos onFulfilledy a los onRejectedcontroladores que se pasan then?
Bergi
Sí, solo cuando promete mezclar el estado fulfilledy rejected. Pero realmente causa un problema difícil ser compatible con todos los casos de uso prometedores normalmente, como onFulfilledy onRejectedtodos devuelven Promise.reject()o Promise.resolve(). Hasta ahora no tengo claro cómo resolverlo, ¿alguien tiene una mejor idea? La mejor respuesta por ahora tiene un problema es que no puede filtrar datos y errores en el entorno del navegador.
wenshin
¿Necesitamos instalar el módulo npm con el administrador de paquetes pip python?
sevenfourk