Gestione degli errori in Promise.all


267

Ho una serie di promesse con cui sto risolvendo Promise.all(arrayOfPromises);

Continuo per continuare la catena di promesse. Sembra qualcosa del genere

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();
});

Voglio aggiungere una dichiarazione catch per gestire una promessa individuale nel caso in cui si verifichi un errore, ma quando provo Promise.allrestituisce il primo errore che trova (ignora il resto), quindi non riesco a ottenere i dati dal resto delle promesse in l'array (che non ha dato errori).

Ho provato a fare qualcosa come ..

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();
});

Ma questo non si risolve.

Grazie!

-

Modificare:

Ciò che le risposte sotto riportate erano completamente vere, il codice si stava rompendo per altri motivi. Nel caso qualcuno fosse interessato, questa è la soluzione con cui ho finito ...

Catena di server 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;
            })
    };

Chiamata API (chiamata route.async)

return async()
    .then(function(result) {
        // dispatch a success
        return result;
    })
    .catch(function(err) {
        // dispatch a failure and throw error
        throw err;
    });

Mettere il .catchfor Promise.allprima del .thensembra aver servito allo scopo di catturare eventuali errori dalle promesse originali, ma poi riportare l'intero array al successivo.then

Grazie!


2
Il tuo tentativo sembra che dovrebbe funzionare ... forse c'è un altro problema da qualche parte dopo?
Ry-

.then(function(data) { return data; })può essere completamente omesso
Bergi,

L'unica ragione per cui quanto sopra non dovrebbe essere risolto è se non ci stai mostrando tutto il codice nei gestori theno catche c'è un errore che viene lanciato all'interno. A proposito, questo nodo?

1
Non hai catture finali nella tua "catena esistente", quindi potrebbero esserci errori che non vedi che potrebbero spiegare perché "non si risolve". Prova ad aggiungerlo e vedi quale errore ricevi.
fiocco

Risposte:


190

Promise.allè tutto o niente. Si risolve una volta che tutte le promesse nell'array vengono risolte o rifiutate non appena una di esse viene rifiutata . In altre parole, o si risolve con un array di tutti i valori risolti o rifiuta con un singolo errore.

Alcune librerie hanno qualcosa chiamato Promise.when, che a mio avviso aspetterebbe invece che tutte le promesse nell'array si risolvano o rifiutino, ma non ne ho familiarità, e non è in ES6.

Il tuo codice

Sono d'accordo con altri qui che la tua correzione dovrebbe funzionare. Dovrebbe risolversi con un array che può contenere una combinazione di valori riusciti e oggetti errori. È insolito passare oggetti di errore nel percorso di successo, ma supponendo che il codice li stia aspettando, non vedo alcun problema.

L'unica ragione per cui riesco a pensare al motivo per cui "non si risolverà" è che non ci riesce nel codice che non ci stai mostrando e il motivo per cui non vedi alcun messaggio di errore a riguardo è perché questa catena di promesse non è terminata con un finale cattura (per quanto ci stai mostrando comunque).

Mi sono preso la libertà di prendere in considerazione la "catena esistente" dal tuo esempio e di terminare la catena con un fermo. Questo potrebbe non essere giusto per te, ma per le persone che leggono questo, è importante restituire o terminare sempre le catene, oppure i potenziali errori, anche gli errori di codifica, verranno nascosti (che è ciò che sospetto sia successo qui):

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
});

4
Tu (e i commenti sopra) avevi ragione. Il mio route.handler.promiseHandler doveva .catch () e restituire l'errore. Avevo anche bisogno di aggiungere l'ultimo .catch () alla fine della catena. Grazie per aver segnalato l'importanza di avere gestori di errori / successi in ogni fase della catena :).
Jon,

2
Ho scoperto anche che se lancio l'errore nel mio .catch () per route.handler.promiseHandler, andrà automaticamente alla cattura finale. Se restituisco invece l'errore, farà quello che voglio e gestirà l'intero array.
Jon,

2
Ora esiste un metodo standard Promise.allSettled()con un supporto decente. Vedi di riferimento .
Andréa Maugars,

Sì, Promise.allnon riesce, quando il primo thread non riesce. Ma sfortunatamente tutti gli altri thread continuano a funzionare fino al termine. Nulla è cancellato, peggio ancora: non c'è modo di annullare un thread Promise. Quindi qualunque cosa i thread stiano facendo (e manipolando) continuano, cambiano stati e variabili, usano la CPU, ma alla fine non restituiscono il loro risultato. È necessario essere consapevoli di ciò per non produrre un caos, ad esempio quando si ripete / si riprova la chiamata.
Marc Wäckerlin,

144

NUOVA RISPOSTA

const results = await Promise.all(promises.map(p => p.catch(e => e)));
const validResults = results.filter(result => !(result instanceof Error));

API FUTURE Promise


11
Anche ese non deve essere un Error. Potrebbe essere una stringa, ad esempio, se qualcuno la restituisce come Promise.reject('Service not available').
Klesun,

@ArturKlesun come potremmo quindi classificare quale promessa ha portato all'errore e quale no?
Shubham Jain,

5
@ shubham-jain con .then()e .catch(). Promise.resolve()passerebbe valore al primo, mentre Promise.reject()lo passerà al secondo. Si possono avvolgere in oggetto, per esempio: p.then(v => ({success: true, value: v})).catch(e => ({success: false, error: e})).
Klesun,

2
Perché dovresti filtrare i risultati? Non ha senso se stai facendo qualcosa con i risultati: hai bisogno dell'ordine per sapere quale valore restituito è da quale promessa!
Ryan Taylor,

21

Per continuare il Promise.allciclo (anche quando una Promessa rifiuta) ho scritto una funzione di utilità che viene chiamata executeAllPromises. Questa funzione di utilità restituisce un oggetto con resultse errors.

L'idea è che tutte le Promesse a cui passerai executeAllPromisessaranno inserite in una nuova Promessa che si risolverà sempre. La nuova Promessa si risolve con un array che ha 2 punti. Il primo punto contiene il valore di risoluzione (se presente) e il secondo punto mantiene l'errore (se la Promessa spostata rifiuta).

Come passaggio finale, executeAllPromisesaccumula tutti i valori delle promesse spostate e restituisce l'oggetto finale con una matrice per resultse una matrice per errors.

Ecco il codice:

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}`);
});


2
Questo può essere fatto più semplice. Vedi stackoverflow.com/a/36115549/918910
fiocco

18

ES2020 introduce un nuovo metodo per il tipo Promise: Promise.allSettled()
Promise.allSettled ti dà un segnale quando tutte le promesse di input sono definite, il che significa che sono state soddisfatte o rifiutate. Ciò è utile nei casi in cui non ti interessa lo stato della promessa, vuoi solo sapere quando il lavoro è finito, indipendentemente dal fatto che abbia avuto successo.

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']

Maggiori informazioni nel post sul blog v8 https://v8.dev/features/promise-combinators


13

Come ha detto @jib,

Promise.all è tutto o niente.

Tuttavia, puoi controllare alcune promesse che sono "autorizzate" a fallire e vorremmo procedere .then.

Per esempio.

  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
  })

6

se riesci ad usare la libreria q https://github.com/kriskowal/q ha il metodo q.allSettled () in grado di risolvere questo problema, puoi gestire ogni promessa a seconda del suo stato o compilato o rifiutato così

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();
});

Dato che stai suggerendo l'uso di alcune librerie ( q), sarebbe più utile se fornissi un esempio di utilizzo relativo alla domanda. Allo stato attuale, la tua risposta non spiega come questa libreria può aiutare a risolvere il problema.
ishmaelMakitla

aggiunto un esempio come suggerito
Mohamed Mahmoud,

1
Intorno al 2018 si dovrebbe sempre vedere cosa Sindre ha a disposizione :-). github.com/sindresorhus/p-settle . Con i moduli monouso Sindre non è necessario importare una libreria enorme come q per un solo bit.
DKebler,

6

Utilizzando Async await -

qui una funzione asincrona func1 sta restituendo un valore risolto, e func2 sta generando un errore e restituendo un valore nullo in questa situazione, possiamo gestirlo come vogliamo e restituire di conseguenza.

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();

L'output è - ['func1', null]


4

Per quelli che usano ES8 che inciampano qui, puoi fare qualcosa di simile al seguente, usando le funzioni asincrone :

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);

3

Siamo in grado di gestire il rifiuto a livello di singole promesse, quindi quando otteniamo i risultati nella nostra matrice dei risultati, sarà l'indice della matrice che è stato rifiutato undefined. Siamo in grado di gestire la situazione in base alle esigenze e utilizzare i risultati rimanenti.

Qui ho rifiutato la prima promessa, quindi viene definita indefinita, ma possiamo usare il risultato della seconda promessa, che è all'indice 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) );
}


Come si può fare una cosa simile se si utilizza async wait?
Rudresh Ajgaonkar,

Ho risposto alla tua domanda, trova il link per la risposta. stackoverflow.com/a/55216763/4079716
Nayan Patel

2

Hai preso in considerazione Promise.prototype.finally()?

Sembra essere progettato per fare esattamente quello che vuoi: eseguire una funzione una volta che tutte le promesse sono state stabilite (risolte / rifiutate), indipendentemente dal fatto che alcune promesse vengano respinte.

Dalla documentazione MDN :

Il finally()metodo può essere utile se si desidera eseguire l'elaborazione o la pulizia una volta stabilita la promessa, indipendentemente dal risultato.

Il finally()metodo è molto simile alla chiamata, .then(onFinally, onFinally)tuttavia ci sono un paio di differenze:

Quando si crea una funzione in linea, è possibile passarla una volta, anziché essere costretta a dichiararla due volte o creare una variabile per essa.

Un callback infine non riceverà alcun argomento, poiché non esiste alcun mezzo affidabile per determinare se la promessa è stata rispettata o respinta. Questo caso d'uso è proprio quando non ti interessa il motivo del rifiuto, o il valore di adempimento, e quindi non è necessario fornirlo.

Diversamente Promise.resolve(2).then(() => {}, () => {})(che verrà risolto con undefined), Promise.resolve(2).finally(() => {})verrà risolto con 2. Allo stesso modo, diversamente Promise.reject(3).then(() => {}, () => {})(che verrà risolto con undefined), Promise.reject(3).finally(() => {})verrà rifiutato con 3.

== Fallback ==

Se la tua versione di JavaScript non supporta Promise.prototype.finally()è possibile utilizzare questa soluzione alternativa da Jake Archibald :Promise.all(promises.map(p => p.catch(() => undefined)));


1
Sì, fino a quando non Promises.allSettled()viene effettivamente implementato (è documentato da MDN qui ), quindi Promises.all.finally()sembrerebbe realizzare la stessa cosa. Sto per provarlo ...
Jamess,

@jamess Perché non fai questo commento come una risposta corretta? Nessuna delle risposte si riferisce a ES6 allSettled().
pravin,

@pravin - Da quello che posso dire, allSettled()non è implementato da nessuna parte (ancora), quindi non voglio andare oltre la realtà. Ho avuto successo Promises.all(myPromiseArray).finally()e questo si adatta a questa risposta. Una volta allSettled()che esiste davvero, allora potrei testarlo e scoprire come funziona effettivamente. Fino ad allora, chi sa cosa implementeranno effettivamente i browser? A meno che tu non abbia recenti informazioni contrarie ...
Jamess,

@jamess È vero che è ancora in fase di bozza .. tuttavia gli ultimi FF e Chrome sembrano supportarlo pienamente .. Non sono sicuro della sua stabilità .. Mozilla Docs Comunque il punto che stavo cercando di fare era che sarebbe stato molto più facile da trovare se fosse una risposta che un commento .. è comunque una chiamata :)
pravin

@pravin - Al momento in cui ho pubblicato il mio commento, non è stato implementato da nessuna parte. Ho appena testato in Firefox e Chrome: Promise.allSettlednon è implementato in Firefox, ma sembra esistere in Chrome. Solo perché i documenti dicono che è implementato non significa che sia davvero implementato. Non lo userò presto.
Jamess,

0

In alternativa, se hai un caso in cui non ti preoccupi particolarmente dei valori delle promesse risolte quando si verifica un errore ma vuoi comunque che vengano eseguite, potresti fare qualcosa del genere che risolverà con le promesse normalmente quando tutti riescono e rifiutano con le promesse fallite quando qualcuno di loro fallisce:

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)
      }
    }
  )
}

0

Puoi sempre racchiudere le tue funzioni di restituzione della promessa in modo tale da rilevare l'errore e restituire invece un valore concordato (ad es. Messaggio di errore), in modo che l'eccezione non si estenda fino alla funzione Promessa.all e la disabiliti.

async function resetCache(ip) {

    try {

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

    }catch (e) {

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

}

0

Ho trovato un modo (soluzione alternativa) per farlo senza farlo sincronizzare.

Quindi, come è stato menzionato prima Promise.all è tutto.

quindi ... Usa una promessa acclusa per catturare e forzare la risoluzione.


      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)

0

Dovresti sapere come identificare un errore nei risultati. Se non si verifica un errore previsto standard, è consigliabile eseguire una trasformazione su ciascun errore nel blocco catch che lo rende identificabile nei risultati.

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
  }

0

Non è il modo migliore per registrare gli errori, ma è sempre possibile impostare tutto su un array per promiseAll e archiviare i risultati risultanti in nuove variabili.

Se usi graphQL devi postelaborare la risposta a prescindere e se non trova il riferimento corretto, bloccherà l'app, restringendo dove si trova il 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;

-1

Ecco come Promise.allè progettato per funzionare. Se una sola promessareject() è, l'intero metodo fallisce immediatamente.

Ci sono casi d'uso in cui si potrebbe desiderare che le Promise.allpromesse vengano meno. Per far sì che ciò accada, semplicemente non usare alcuna reject()dichiarazione nella tua promessa. Tuttavia, per assicurarti che la tua app / script non si blocchi nel caso in cui una singola promessa sottostante non ottenga mai una risposta, devi mettere un timeout su di essa.

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);
        });
    });
}


Non utilizzare reject()nella promessa va bene, ma cosa succede se è necessario utilizzare le promesse di un'altra libreria?
Dan Dascalescu,

-8

Ho scritto una libreria npm per affrontare questo problema in modo più bello. https://github.com/wenshin/promiseallend

Installare

npm i --save promiseallend

25/02/2017 nuova api, non rompere i principi della promessa

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))

--------------------------------

Vecchia cattiva API, non usarla!

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'}

Come funziona? Mostra e spiega l'implementazione della funzione.
Bergi,

Ho scritto una nuova logica concorrente come Promise.all. Ma raccoglierà tutti i dati e gli errori di ogni promessa. supporta anche input di oggetti, non è un punto. dopo aver raccolto tutti i dati e gli errori, sovrascrivo il promise.thenmetodo per gestire i callback registrati che includono respinti e soddisfatti. Per i dettagli puoi vedere il codice
wenshin,

Uh, che il codice chiamerà entrambi onFulfillede onRejectedgestori che sono passati a then?
Bergi,

Sì, solo quando lo stato della promessa si mescola fulfillede rejected. Ma in realtà causa un problema difficile essere compatibile con tutti i casi d'uso promettenti normalmente, come onFulfillede onRejectedtutti restituiscono Promise.reject()o Promise.resolve(). Finora non sono chiaro come risolverlo, qualcuno ha un'idea migliore? La risposta migliore per ora ha un problema è che potrebbe non essere in grado di filtrare i dati e gli errori nell'ambiente del browser.
Wenshin,

Dobbiamo installare il modulo npm con il gestore pacchetti pip python?
Sevenfourk
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.