Manipulando erros no Promise.all

267

Tenho uma série de promessas que estou resolvendo com Promise.all(arrayOfPromises);

Eu continuo a cadeia de promessas. Parece algo como isto

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

Quero adicionar uma instrução catch para lidar com uma promessa individual, caso ocorra um erro, mas, quando tento, Promise.allretorna o primeiro erro encontrado (desconsidera o restante) e não consigo obter os dados do restante das promessas em a matriz (que não errou).

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

Mas isso não resolve.

Obrigado!

-

Editar:

O que as respostas abaixo disseram eram completamente verdadeiras, o código estava quebrando devido a outros motivos. Caso alguém esteja interessado, esta é a solução que eu acabei com ...

Cadeia de servidores Node Express

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

Chamada de API (rota.async chamada)

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

Colocar o .catchfor Promise.allantes do .thenparece ter servido ao objetivo de capturar quaisquer erros das promessas originais, mas retornar a matriz inteira para a próxima.then

Obrigado!

Jon
fonte
2
Sua tentativa parece que deveria funcionar ... talvez haja outro problema em algum lugar mais tarde?
Ry-
.then(function(data) { return data; })pode ser completamente omitido
Bergi
A única razão pela qual o procedimento acima não deve ser resolvido é se você não estiver nos mostrando todo o código nos manipuladores thenou catche se houver um erro dentro dele. A propósito, esse nó é?
1
Você não tem uma captura final em sua "cadeia existente"; portanto, pode haver erros que você não está vendo, o que explica por que "não resolve". Tente adicionar isso e veja qual erro você recebe.
jib
Aqui está a resposta: stackoverflow.com/questions/31424561/…
Humoyun Ahmad

Respostas:

190

Promise.allé tudo ou nada. Ele resolve quando todas as promessas da matriz são resolvidas ou rejeitadas assim que uma delas rejeitar. Em outras palavras, ele resolve com uma matriz de todos os valores resolvidos ou rejeita com um único erro.

Algumas bibliotecas têm algo chamado Promise.when, que eu entendo esperaria que todas as promessas na matriz resolvessem ou rejeitassem, mas não estou familiarizado com isso e não está no ES6.

Seu código

Concordo com outras pessoas aqui que sua correção deve funcionar. Ele deve resolver com uma matriz que pode conter uma mistura de valores bem-sucedidos e objetos de erros. É incomum passar objetos de erro no caminho do sucesso, mas, supondo que seu código os esteja esperando, não vejo problema nisso.

A única razão pela qual consigo pensar por que "não resolveria" é que está falhando no código que você não está nos mostrando e o motivo pelo qual não está recebendo nenhuma mensagem de erro sobre isso é porque essa cadeia de promessas não é finalizada com uma final catch (tanto quanto o que você está nos mostrando).

Tomei a liberdade de levar em consideração a "corrente existente" do seu exemplo e encerrar a corrente com uma pegadinha. Isso pode não ser adequado para você, mas para as pessoas que estão lendo isso, é importante sempre retornar ou encerrar cadeias, ou erros em potencial, até mesmo erros de codificação, ficarão ocultos (que é o que suspeito que aconteceu aqui):

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
});
jib
fonte
4
Você (e os comentários acima) estavam certos. Meu route.handler.promiseHandler precisava .catch () e retornar o erro. Eu também precisava adicionar o .catch () final ao final da cadeia. Obrigado por retransmitir a importância de ter manipuladores de sucesso / erro em cada etapa da cadeia :).
21415 Jon
2
Descobri também que, se eu lançar o erro no meu .catch () para route.handler.promiseHandler, ele irá automaticamente para a captura final. Se eu retornar o erro, ele fará o que eu quero e manipulará toda a matriz.
21415 Jon
2
Agora existe um método padrão Promise.allSettled()com suporte decente. Veja referência .
Andréa Maugars
Sim, Promise.allfalha quando o primeiro thread falha. Infelizmente, porém, todos os outros threads ainda continuam em execução até serem concluídos. Nada é cancelado, ainda pior: não há como cancelar um thread Promise. Portanto, o que quer que os threads estejam fazendo (e manipulando) eles continuam, eles mudam de estado e variável, usam CPU, mas no final não retornam seu resultado. Você precisa estar ciente disso para não causar um caos, por exemplo, quando você repete / repete a chamada.
Marc Wäckerlin
144

NOVA RESPOSTA

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

API do FUTURE Promise

Solominh
fonte
11
Embora enão precise ser um Error. Pode ser uma string, por exemplo, se alguém retornar como ela Promise.reject('Service not available').
Klesun 27/02/19
@ArturKlesun, como poderíamos classificar qual das promessas resultou em erro e quais não?
Shubham Jain
5
@ shubham-jain com .then()e .catch(). Promise.resolve()passaria valor para o primeiro, enquanto Promise.reject()passaria para o último. Você pode envolvê-los em objeto, por exemplo: p.then(v => ({success: true, value: v})).catch(e => ({success: false, error: e})).
Klesun 13/05/19
2
Por que você filtraria os resultados? Isso não faz sentido se você estiver fazendo alguma coisa com os resultados - você precisa saber para qual valor retornado é qual promessa!
Ryan Taylor
21

Para continuar o Promise.allloop (mesmo quando uma promessa rejeita), escrevi uma função de utilitário chamada executeAllPromises. Esta função de utilitário retorna um objeto com resultse errors.

A idéia é que todas as promessas executeAllPromisespelas quais você passa sejam envolvidas em uma nova promessa que sempre será resolvida. A nova promessa resolve com uma matriz que tem 2 pontos. O primeiro ponto mantém o valor de resolução (se houver) e o segundo ponto mantém o erro (se a Promessa finalizada rejeitar).

Como etapa final, executeAllPromisesacumula todos os valores das promessas agrupadas e retorna o objeto final com uma matriz para resultse uma matriz para errors.

Aqui está o 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
fonte
2
Isso pode ser feito de maneira mais simples. Veja stackoverflow.com/a/36115549/918910
jib
18

O ES2020 apresenta um novo método para o tipo Promise: Promise.allSettled()
Promise.allSettled fornece um sinal quando todas as promessas de entrada são liquidadas, o que significa que elas são cumpridas ou rejeitadas. Isso é útil nos casos em que você não se importa com o estado da promessa, apenas quer saber quando o trabalho está concluído, independentemente de ter sido bem-sucedido.

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

Leia mais na postagem do blog da v8 https://v8.dev/features/promise-combinators

MosheZada
fonte
13

Como o @jib disse,

Promise.all é tudo ou nada.

No entanto, você pode controlar certas promessas que "podem" falhar e gostaríamos de prosseguir .then.

Por exemplo.

  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
  })
Herman
fonte
6

se você usar a biblioteca q https://github.com/kriskowal/q, ela possui o método q.allSettled () que pode solucionar esse problema, você pode lidar com todas as promessas, dependendo de seu estado, sendo preenchidas ou rejeitadas

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
fonte
Como você está sugerindo o uso de alguma biblioteca ( q), seria mais útil se você fornecesse um exemplo de uso relacionado à pergunta. Como está, sua resposta não explica como essa biblioteca pode ajudar a resolver o problema.
ishmaelMakitla
adicionou um exemplo como sugerido
Mohamed Mahmoud
1
Por volta de 2018, deve-se sempre ver o que o Sindre tem disponível :-). github.com/sindresorhus/p-settle . Com os módulos de uso único do Sindre, você não precisa importar uma biblioteca enorme como o q por apenas um bit.
DKebler 19/05/19
6

Usando o Async aguardam -

aqui uma função assíncrona func1 está retornando um valor resolvido, e func2 está gerando um erro e retornando um nulo nessa situação, podemos lidar com isso como queremos e retornar adequadamente.

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

A saída é - ['func1', nulo]

Nayan Patel
fonte
4

Para aqueles que usam o ES8 que tropeçam aqui, você pode fazer algo como o seguinte, usando funções assí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
fonte
3

Podemos lidar com a rejeição no nível de promessas individuais; portanto, quando obtivermos os resultados em nossa matriz de resultados, o índice da matriz que foi rejeitada será undefined . Podemos lidar com essa situação conforme necessário e usar os resultados restantes.

Aqui eu rejeitei a primeira promessa, então ela é indefinida, mas podemos usar o resultado da segunda promessa, que está no í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
fonte
Como você pode fazer uma coisa semelhante se usarmos o Async Aguardar?
Rudresh Ajgaonkar
Respondi sua pergunta, encontre o link para a resposta. stackoverflow.com/a/55216763/4079716
Nayan Patel
2

Você já considerou Promise.prototype.finally()?

Parece ter sido projetado para fazer exatamente o que você deseja - executar uma função depois que todas as promessas forem estabelecidas (resolvidas / rejeitadas), independentemente de algumas das promessas serem rejeitadas.

Na documentação MDN :

o finally() método pode ser útil se você desejar fazer algum processamento ou limpeza assim que a promessa for estabelecida, independentemente do resultado.

O finally()método é muito semelhante a chamar.then(onFinally, onFinally) mas existem algumas diferenças:

Ao criar uma função embutida, você pode transmiti-la uma vez, em vez de ser forçado a declará-la duas vezes ou criar uma variável para ela.

Um retorno de chamada finalmente não receberá nenhum argumento, pois não há meios confiáveis ​​para determinar se a promessa foi cumprida ou rejeitada. Esse caso de uso é exatamente quando você não se importa com o motivo da rejeição ou o valor da satisfação e, portanto, não há necessidade de fornecê-lo.

Diferente Promise.resolve(2).then(() => {}, () => {})(que será resolvido com indefinido), Promise.resolve(2).finally(() => {})será resolvido com 2. Da mesma forma, diferente de Promise.reject(3).then(() => {}, () => {})(que será cumprido com indefinido), Promise.reject(3).finally(() => {})será rejeitado com 3.

== Fallback ==

Se sua versão do JavaScript não suportar, Promise.prototype.finally()você pode usar esta solução alternativa de Jake Archibald :Promise.all(promises.map(p => p.catch(() => undefined)));

Tom Auger
fonte
1
Sim, até que Promises.allSettled()seja realmente implementado (documentado pelo MDN aqui ), Promises.all.finally()parece que ele realizará a mesma coisa. Estou prestes a dar-lhe uma tentativa ...
jamess
@jamess Por que você não faz este comentário como uma resposta adequada? Nenhuma das respostas se refere ao ES6 allSettled().
Pravin
@pravin - Pelo que posso dizer, allSettled()ainda não está implementado em nenhum lugar, então não quero ficar à frente da realidade. Eu tive sucesso com Promises.all(myPromiseArray).finally(), e isso se encaixa com esta resposta. Uma vez que allSettled()realmente exista, posso testá-lo e descobrir como ele realmente funciona. Até então, quem sabe o que os navegadores realmente implementarão? A menos que você tem informação recente ao contrário ...
jamess
@jamess Verdade que o seu ainda em fase de projecto .. no entanto mais recente FF e cromo parece apoiá-lo totalmente .. Não tenho certeza de que a estabilidade tho .. Mozilla Docs De qualquer forma o ponto que eu estava tentando fazer era que ele teria muito mais fácil encontrar se era uma resposta do que um comentário .. é chamada ur embora :)
Pravin
@pravin - No momento em que postei meu comentário, ele não foi implementado em nenhum lugar. Acabei de testar no Firefox e Chrome: Promise.allSettlednão está implementado no Firefox, mas parece existir no Chrome. Só porque os documentos dizem que foi implementado não significa que realmente seja implementado. Não vou usá-lo tão cedo.
jamess
0

Como alternativa, se você tiver um caso em que não se preocupa particularmente com os valores das promessas resolvidas quando há uma falha, mas ainda deseja que elas sejam executadas, você pode fazer algo assim que resolverá com as promessas normalmente quando todos têm sucesso e rejeitam as promessas fracassadas quando alguma delas falha:

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
fonte
0

Você sempre pode agrupar sua promessa retornando funções de uma maneira que elas detectem falhas e retornem um valor acordado (por exemplo, mensagem de erro), para que a exceção não role até a função Promise.all e a desative.

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
fonte
0

Eu encontrei uma maneira (solução alternativa) de fazer isso sem torná-lo sincronizado.

Então, como foi mencionado antes, não Promise.allhá nada.

então ... Use uma promessa anexa para capturar e forçar a resolução.


      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 Sebastian Contreras Aceve
fonte
0

Você precisaria saber como identificar um erro nos seus resultados. Se você não tiver um erro esperado padrão, sugiro que você execute uma transformação em cada erro no bloco de captura que o identifique nos 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
fonte
0

Não é a melhor maneira de registrar erros, mas sempre é possível definir tudo para uma matriz para o promessaTodos e armazenar os resultados resultantes em novas variáveis.

Se você usa o graphQL, precisa pós-processar a resposta independentemente e, se ela não encontrar a referência correta, ele travará o aplicativo, restringindo a localização do 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
fonte
-1

É assim que Promise.allé projetado para funcionar. Se uma única promessa for cumprida reject(), todo o método falhará imediatamente.

Existem casos de uso em que se pode querer que as Promise.allpromessas falhem. Para fazer isso acontecer, simplesmente não use nenhuma reject()declaração em sua promessa. No entanto, para garantir que seu aplicativo / script não congele, caso nenhuma promessa subjacente nunca receba uma resposta, é necessário colocar um tempo limite nela.

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
fonte
aqui está a resposta: stackoverflow.com/questions/31424561/...
Humoyun Ahmad
Não usar reject()sua promessa é bom, mas e se você precisar usar as promessas de outra biblioteca?
Dan Dascalescu 03/02/19
-8

Eu escrevi uma biblioteca npm para lidar com esse problema mais bonito. https://github.com/wenshin/promiseallend

Instalar

npm i --save promiseallend

25-02-2017 nova API, não são princípios de promessa de quebra

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

—————————————————————————

API ruim antiga, não a use!

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
fonte
Como funciona? Por favor, mostre e explique sua implementação da função.
Bergi 16/05
Eu escrevi uma nova lógica simultânea como Promise.all. Mas ele coletará todos os dados e erros de todas as promessas. também suporta entrada de objetos, não faz sentido. depois de coletar todos os dados e erros, substituo o promise.thenmétodo para lidar com os retornos de chamada registrados, que incluem rejeitados e atendidos. Para mais detalhes você pode ver o código
wenshin
Esse código chamará ambos onFulfillede onRejectedmanipuladores que são passados ​​para then?
Bergi 17/05
Sim, somente quando o status da promessa for mixado fulfillede rejected. Mas, na verdade, faz com que um problema difícil seja compatível com todos os casos de uso prometidos normalmente, como onFulfillede onRejectedtodos retornam Promise.reject()ou Promise.resolve(). Até agora não estou claro como resolvê-lo, alguém tem uma idéia melhor? A melhor resposta por enquanto tem um problema é que ele pode não filtrar dados e erros no ambiente do navegador.
Wenshin 17/05
Precisamos instalar o módulo npm com o gerenciador de pacotes pip python?
precisa saber é o seguinte