Espere até que todos os ES6 prometem promises completas, até rejeitadas

Vamos dizer que tenho um conjunto de promises que estão fazendo solicitações de rede, das quais uma falhará:

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

Vamos dizer que eu quero esperar até que tudo isso tenha terminado, independentemente de um deles ter falhado. Pode haver um erro de rede para um recurso que eu possa viver sem, mas que, se puder, quero antes de continuar. Eu quero lidar com falhas de rede graciosamente.

Como o Promises.all não deixa espaço para isso, qual é o padrão recomendado para lidar com isso, sem usar uma biblioteca de promises?

Claro, você só precisa de um reflect :

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

Ou com o ES5:

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

Ou no seu exemplo:

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

Resposta semelhante, mas mais idiomática para ES6 talvez:

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

A resposta de Benjamin oferece uma grande abstração para resolver esse problema, mas eu esperava por uma solução menos abstrata. A maneira explícita de resolver esse problema é simplesmente chamar .catch nas promises internas e retornar o erro de seu retorno de chamada.

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

Levando isso um passo adiante, você poderia escrever um manipulador de captura genérico que se parece com isso:

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

então você pode fazer

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

O problema com isso é que os valores capturados terão uma interface diferente dos valores não capturados, portanto, para limpar isso, você pode fazer algo como:

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

Então agora você pode fazer isso:

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

Então, para mantê-lo seco, você chega à resposta de Benjamin:

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

onde agora parece

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

Os benefícios da segunda solução são que ela é abstraída e seca. A desvantagem é que você tem mais código e precisa lembrar-se de refletir todas as suas promises para tornar as coisas consistentes.

Eu caracterizaria minha solução como explícita e KISS, mas na verdade menos robusta. A interface não garante que você saiba exatamente se a promise foi bem-sucedida ou não.

Por exemplo, você pode ter isto:

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

Isso não será pego por a.catch .

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

Não há como saber qual foi fatal e qual não foi. Se isso é importante, então você vai querer impor e interface que controla se foi bem-sucedido ou não (o que reflect ).

Se você quiser apenas manipular erros graciosamente, poderá simplesmente tratar os erros como valores indefinidos:

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

No meu caso, eu não preciso saber o erro ou como ele falhou - eu apenas me importo se eu tenho o valor ou não. Vou deixar a function que gera a promise se preocupar com o registro do erro específico.

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

Dessa forma, o restante do aplicativo pode ignorar seu erro, se desejar, e tratá-lo como um valor indefinido, se desejar.

Eu quero que minhas funções de alto nível falhem com segurança e não me preocupem com os detalhes de por que suas dependencies falharam, e eu também prefiro o KISS to DRY quando eu tiver que fazer essa troca - que é por isso que optei por não reflect .

Eu realmente gosto da resposta de Benjamin, e como ele basicamente transforma todas as promises em sempre-resolvendo-mas-às vezes-com-erro-como-um-resultado. 🙂
Aqui está minha tentativa em seu pedido apenas no caso de você estar procurando alternativas. Esse método simplesmente trata os erros como resultados válidos e é codificado como Promise.all .

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

Eu tive o mesmo problema e resolvi da seguinte maneira:

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

Nesse caso, o Promise.all esperará que cada Promessa seja resolved ou rejected .

E tendo esta solução estamos “parando a execução de catch ” de uma maneira não-bloqueante. Na verdade, não estamos parando nada, apenas retornando o Promise em um estado pendente que retorna outra Promise quando é resolvido após o tempo limite.

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

O Promise.all engolirá qualquer promise rejeitada e armazenará o erro em uma variável, então retornará quando todas as promises forem resolvidas. Então você pode voltar a lançar o erro ou fazer o que for. Desta forma, eu acho que você sairia da última rejeição em vez da primeira.

Isso deve ser consistente com a forma como Q faz isso :

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

Você pode executar sua lógica sequencialmente por meio do executor síncrono nsynjs . Ele fará uma pausa em cada promise, aguardará resolução / rejeição e atribuirá o resultado da resolução à propriedade de data ou lançará uma exceção (para manipular você precisará de um bloco try / catch). Aqui está um exemplo:

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

Acabei de escrever a function custom promise.all() e usá-la em vez do padrão Promise.all() . Se todas as promises forem resolvidas, ela funcionará exatamente como a padrão. Se uma das mais promises for rejeitada, ela retorna a primeira rejeitada da mesma forma que a padrão, mas ao contrário, ela espera que todas as promises resolvam / rejeitem:

 var promise = {}; // custom namespace for promise-related stuff promise.all = function(values) { var e; return Promise.all(values.map(function(value) { return Promise.resolve(value).catch(function(error) { e = e || error; }); })).then(function(values2) { if(e) throw e; return values2; }); }; 

Podemos ir ainda mais longe para estender gentilmente o padrão Promise.all() com um parâmetro adicional de wait como este:

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

Isso pode ser razoável, pois a nova versão é compatível com a versão padrão, estendendo sua funcionalidade. (Podemos evitar completamente o namespace de promise personalizada – ele é usado apenas para mostrar a ideia.)

Pessoas que estão desenvolvendo padrões – por que não include isso em um novo padrão Promise?

Eu faria:

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

Minha abordagem:

 const promise1 = Promise.resolve(1) const promise2 = Promise.reject(2) const errors = [] Promise.all([ promise1, promise2 ].map((promise) => promise.catch((error) => { errors.push(error) }))).then(([response1, response2]) => { console.log(response1) // 1 console.log(response2) // undefined console.log(errors) // [2] }) 

Tem suas próprias desvantagens, mas em geral pode ser eficaz. Eu costumo usar esse padrão quando não me importo com as razões do erro.

Eu não sei qual biblioteca de promises você está usando, mas a maioria tem algo parecido com o AllSettled .

Edit: Ok, desde que você deseja usar o ES6 simples, sem bibliotecas externas, não existe tal método.

Em outras palavras: você precisa passar por cima de suas promises manualmente e resolver uma nova promise combinada assim que todas as promises forem cumpridas.