Primeira diferença - falha rapidamente
Concordo com a resposta de @ zzzzBov, mas a vantagem "falhar rápido" do Promise.all não é apenas a única diferença. Alguns usuários nos comentários perguntam por que usar Promise.all quando é apenas mais rápido no cenário negativo (quando alguma tarefa falha). E eu pergunto por que não? Se eu tenho duas tarefas paralelas assíncronas independentes e a primeira é resolvida em muito tempo, mas a segunda é rejeitada em muito pouco tempo, por que deixar o usuário esperar pela mensagem de erro "tempo muito longo" em vez de "tempo muito curto"? Em aplicações da vida real, devemos considerar um cenário negativo. Mas tudo bem - nesta primeira diferença, você pode decidir qual alternativa usar Promise.all vs. multiple aguardam.
Segunda diferença - tratamento de erros
Mas, ao considerar o tratamento de erros, DEVE usar o Promise.all. Não é possível manipular corretamente erros de tarefas paralelas assíncronas acionadas com espera múltipla. No cenário negativo, você sempre terminará com UnhandledPromiseRejectionWarning
e, PromiseRejectionHandledWarning
embora use o try / catch em qualquer lugar. É por isso que Promise.all foi projetado. É claro que alguém poderia dizer que podemos suprimir que os erros usando process.on('unhandledRejection', err => {})
e process.on('rejectionHandled', err => {})
, mas não é uma boa prática. Encontrei muitos exemplos na internet que não consideram o tratamento de erros para duas ou mais tarefas paralelas assíncronas independentes ou o consideram de maneira errada - basta usar try / catch e esperar que ele consiga detectar erros. É quase impossível encontrar boas práticas. É por isso que estou escrevendo esta resposta.
Resumo
Nunca use múltiplo aguarde por duas ou mais tarefas paralelas assíncronas independentes, pois você não poderá lidar com erros seriamente. Sempre use Promise.all () para este caso de uso.
Async / waitit não substitui Promessas. É muito bonito como usar promessas ... código assíncrono é escrito em estilo de sincronização e podemos evitar váriosthen
promessas.
Algumas pessoas dizem que usando Promise.all () não podemos lidar com erros de tarefas separadamente, mas apenas com erros da primeira promessa rejeitada (sim, alguns casos de uso podem exigir tratamento separado, por exemplo, para registro). Não é problema - consulte o título "Adição" abaixo.
Exemplos
Considere esta tarefa assíncrona ...
const task = function(taskNum, seconds, negativeScenario) {
return new Promise((resolve, reject) => {
setTimeout(_ => {
if (negativeScenario)
reject(new Error('Task ' + taskNum + ' failed!'));
else
resolve('Task ' + taskNum + ' succeed!');
}, seconds * 1000)
});
};
Quando você executa tarefas em um cenário positivo, não há diferença entre Promise.all e vários aguardam. Ambos os exemplos terminam Task 1 succeed! Task 2 succeed!
após 5 segundos.
// Promise.all alternative
const run = async function() {
// tasks run immediate in parallel and wait for both results
let [r1, r2] = await Promise.all([
task(1, 5, false),
task(2, 5, false)
]);
console.log(r1 + ' ' + r2);
};
run();
// at 5th sec: Task 1 succeed! Task 2 succeed!
// multiple await alternative
const run = async function() {
// tasks run immediate in parallel
let t1 = task(1, 5, false);
let t2 = task(2, 5, false);
// wait for both results
let r1 = await t1;
let r2 = await t2;
console.log(r1 + ' ' + r2);
};
run();
// at 5th sec: Task 1 succeed! Task 2 succeed!
Quando a primeira tarefa leva 10 segundos no cenário positivo e a tarefa segundos leva 5 segundos no cenário negativo, existem diferenças nos erros emitidos.
// Promise.all alternative
const run = async function() {
let [r1, r2] = await Promise.all([
task(1, 10, false),
task(2, 5, true)
]);
console.log(r1 + ' ' + r2);
};
run();
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// multiple await alternative
const run = async function() {
let t1 = task(1, 10, false);
let t2 = task(2, 5, true);
let r1 = await t1;
let r2 = await t2;
console.log(r1 + ' ' + r2);
};
run();
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
// at 10th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
Já devemos notar aqui que estamos fazendo algo errado ao usar vários esperam em paralelo. Obviamente, para evitar erros, devemos lidar com isso! Vamos tentar...
// Promise.all alternative
const run = async function() {
let [r1, r2] = await Promise.all([
task(1, 10, false),
task(2, 5, true)
]);
console.log(r1 + ' ' + r2);
};
run().catch(err => { console.log('Caught error', err); });
// at 5th sec: Caught error Error: Task 2 failed!
Como você pode ver para tratar com êxito do erro, precisamos adicionar apenas uma captura à run
função e o código com lógica de captura está no retorno de chamada ( estilo assíncrono ). Não precisamos manipular erros dentro da run
função porque a função assíncrona faz automaticamente - prometer rejeição da task
função causa rejeição da run
função. Para evitar retorno de chamada, podemos usar o estilo de sincronização (assíncrono / aguardar + tentar / capturar), try { await run(); } catch(err) { }
mas neste exemplo não é possível porque não podemos usar await
no encadeamento principal - ele pode ser usado apenas na função assíncrona (é lógico, porque ninguém quer bloquear a linha principal). Para testar se o tratamento funciona no estilo de sincronização , podemos chamarrun
função de uma outra função assíncrono ou uso IIFE (Imediatamente Invoked Função Expressão): (async function() { try { await run(); } catch(err) { console.log('Caught error', err); }; })();
.
Essa é apenas uma maneira correta de executar duas ou mais tarefas paralelas assíncronas e manipular erros. Você deve evitar exemplos abaixo.
// multiple await alternative
const run = async function() {
let t1 = task(1, 10, false);
let t2 = task(2, 5, true);
let r1 = await t1;
let r2 = await t2;
console.log(r1 + ' ' + r2);
};
Podemos tentar manipular o código acima de várias maneiras ...
try { run(); } catch(err) { console.log('Caught error', err); };
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled
... nada foi pego porque ele lida com código de sincronização, mas run
é assíncrono
run().catch(err => { console.log('Caught error', err); });
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: Caught error Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
... Wtf? Vimos, em primeiro lugar, que o erro da tarefa 2 não foi tratado e, posteriormente, foi capturado. Enganador e ainda cheio de erros no console. Inutilizável dessa maneira.
(async function() { try { await run(); } catch(err) { console.log('Caught error', err); }; })();
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: Caught error Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
... O mesmo que acima. O usuário @Qwerty, em sua resposta excluída, perguntou sobre esse comportamento estranho que parece ter sido detectado, mas também há erros não tratados. Nós capturamos o erro porque run () é rejeitado on-line com a palavra-chave wait e pode ser capturado usando try / catch ao chamar run (). Também recebemos erro não tratado porque estamos chamando a função de tarefa assíncrona de forma síncrona (sem palavra-chave aguardada) e essa tarefa é executada fora da função run () e também falha fora. É semelhante quando não somos capazes de lidar com erro try / catch ao chamar alguma função sync qual parte do código é executado em setTimeout ... function test() { setTimeout(function() { console.log(causesError); }, 0); }; try { test(); } catch(e) { /* this will never catch error */ }
.
const run = async function() {
try {
let t1 = task(1, 10, false);
let t2 = task(2, 5, true);
let r1 = await t1;
let r2 = await t2;
}
catch (err) {
return new Error(err);
}
console.log(r1 + ' ' + r2);
};
run().catch(err => { console.log('Caught error', err); });
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
... "apenas" dois erros (o terceiro está faltando), mas nada foi detectado.
Adição (manipule os erros da tarefa separadamente e também o erro de primeira falha)
const run = async function() {
let [r1, r2] = await Promise.all([
task(1, 10, true).catch(err => { console.log('Task 1 failed!'); throw err; }),
task(2, 5, true).catch(err => { console.log('Task 2 failed!'); throw err; })
]);
console.log(r1 + ' ' + r2);
};
run().catch(err => { console.log('Run failed (does not matter which task)!'); });
// at 5th sec: Task 2 failed!
// at 5th sec: Run failed (does not matter which task)!
// at 10th sec: Task 1 failed!
... observe que neste exemplo eu usei negativeScenario = true para ambas as tarefas para melhor demonstração do que acontece ( throw err
é usado para disparar o erro final)