Artigo original: All you need to know about Promise.all
Promises em JavaScript são uma das APIs mais poderosas e que nos auxiliam com operações assíncronas.
A Promise.all leva as operações assíncronas para um novo patamar, pois ajudam você a agregar um grupo de promises.
Em outras palavras, elas ajudam você a realizar operações concorrentes (às vezes, de graça).
Pré-requisitos:
Você precisa conhecer o que é uma Promise em JavaScript.
O que é Promise.all?
Promise.all é, de fato, uma promise que recebe um array de promises como entrada (um iterável). Em seguida, ela é resolvida quando todas as promises são resolvidas ou quando qualquer uma delas é rejeitada.
Por exemplo, vamos supor que você tenha dez promises (operação assíncrona para realizar uma chamada à rede ou uma conexão com um banco de dados). Você precisa saber quando todas as promises forem resolvidas ou terá de esperar até que todas elas sejam resolvidas. Desse modo, você passa todas as promises para Promise.all. Depois, a própria Promise.all vira uma promise, que será resolvida quando todas as 10 promises forem resolvidas ou quando qualquer uma delas for rejeitada com um erro.
Vejamos isso no código:
Promise.all([Promise1, Promise2, Promise3])
.then(result) => {
console.log(result)
})
.catch(error => console.log(`Erro nas promises ${error}`))
Como você pode ver, estamos passando um array para Promise.all. Quando as três promises forem resolvidas, Promise.all resolve e o resultado é enviado para o console.
Vejamos um exemplo:
// Uma promise simples que se resolve após certo tempo
const timeOut = (t) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`Concluída em ${t}`)
}, t)
})
}
// Resolvendo uma promise normal.
timeOut(1000)
.then(result => console.log(result)) // Concluída em 1000
// Promise.all
Promise.all([timeOut(1000), timeOut(2000)])
.then(result => console.log(result)) // ["Concluída em 1000", "Concluída em 2000"]
No exemplo acima, Promise.all resolve após 2000 ms e o resultado é exibido no console como um array.
Algo interessante sobre Promise.all é o fato de que a ordem das promises é mantida. A primeira promise no array será resolvida como o primeiro elemento do array resultante, a segunda promise será o segundo elemento do array resultante e assim por diante.
Vejamos outro exemplo:
// Uma promise simples que é resolvida após certo tempo
const timeOut = (t) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`Concluída em ${t}`)
}, t)
})
}
const durations = [1000, 2000, 3000]
const promises = []
durations.map((duration) => {
// Na linha abaixo, duas coisas ocorrem.
// 1. Estamos chamando a função assíncrona (timeout()). Assim, neste ponto, a função assíncrona começou e entra no estado 'pending' (pendente).
// 2. Estamos enviando a promise pendente ("pending") para um array.
promises.push(timeOut(duration))
})
console.log(promises) // [ Promise { "pending" }, Promise { "pending" }, Promise { "pending" } ]
// Estamos passando um array de promises pendentes para Promise.all
// Promise.all aguardará até que todas as promises sejam resolvidas e, então, ela mesma é resolvida.
Promise.all(promises)
.then(response => console.log(response)) // ["Concluída em 1000", "Concluída em 2000", "Concluída em 3000"]
A partir do exemplo acima, fica claro que Promise.all aguarda até que todas as promises sejam resolvidas.
Vamos ver o que acontece se uma das promises é rejeitada.
// Uma promise simples que é resolvida após certo tempo
const timeOut = (t) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (t === 2000) {
reject(`Rejeitada em ${t}`)
} else {
resolve(`Concluída em ${t}`)
}
}, t)
})
}
const durations = [1000, 2000, 3000]
const promises = []
durations.map((duration) => {
promises.push(timeOut(duration))
})
// Estamos passando um array de promises pendentes para Promise.all
Promise.all(promises)
.then(response => console.log(response)) // Promise.all não pode ser resolvida, já que uma das promises passadas foi rejeitada.
.catch(error => console.log(`Erro ao executar ${error}`)) // Promise.all lança um erro.
Como podemos ver, se uma das promises falhar, o resto das promises também falhará. Promise.all será, desse modo, rejeitada.
Para alguns casos de uso, isso não é necessário. Você precisa executar todas as promises, mesmo que alguma delas falhe, ou, talvez, você possa lidar com as promessas que falham mais tarde.
Vejamos como lidar com isso.
const durations = [1000, 2000, 3000]
promises = durations.map((duration) => {
return timeOut(duration).catch(e => e) // Lidando com o erro de cada promise.
})
Promise.all(promises)
.then(response => console.log(response)) // ["Concluída em 1000", "Rejeitada em 2000", "Concluída em 3000"]
.catch(error => console.log(`Erro ao executar ${error}`))
Casos de uso de Promise.all
Considere que você tenha de realizar um grande número de operações assíncronas, como enviar e-mails comerciais em massa para milhares de usuários.
Um pseudocódigo simples disso seria:
for (let i=0;i<50000; i += 1) {
sendMailForUser(user[i]) // Operação assíncrona de envio de e-mail
}
O exemplo acima é direto ao ponto, mas não tem um bom desempenho. A stack se tornará muito pesada em algum momento. O JavaScript terá um número enorme de conexões HTTP abertas, o que acabará encerrando o servidor.
Uma abordagem simples e com bom desempenho seria fazer a tarefa em lotes. Pegue os primeiros 500 usuários, dispare o e-mail e aguarde até que todas as conexões HTTP sejam fechadas. Em seguida, pegue o próximo lote para processar e assim por diante.
Vejamos um exemplo:
// Função assíncrona para enviar e-mails a uma lista de usuários.
const sendMailForUsers = async (users) => {
const usersLength = users.length
for (let i = 0; i < usersLength; i += 100) {
const requests = users.slice(i, i + 100).map((user) => { // O tamanho do lote é 100. Estamos processando um conjunto de 100 usuários por vez.
return triggerMailForUser(user) // Função assíncrona para enviar o e-mail.
.catch(e => console.log(`Error in sending email for ${user} - ${e}`)) // Faça o catch do erro se lago der errado. Assim, não bloqueamos o laço.
})
// As solicitações terão 100 promises pendentes, no máximo.
// Promise.all aguardará até que todas as promises sejam resolvidas e daí pegará os próximos 100 usuários.
await Promise.all(requests)
.catch(e => console.log(`Erro ao enviar e-mail para o lote ${i} - ${e}`)) // Fazendo o catch do erro.
}
}
sendMailForUsers(userLists)
Vamos considerar um outro cenário: você tem de criar uma API que recebe informações de diversas APIs de terceiros e agregar todas as respostas das APIs.
Promise.all é a maneira perfeita de se fazer isso. Vejamos como.
// Função para fazer o fetch das informações de um usuário no Github.
const fetchGithubInfo = async (url) => {
console.log(`Fetching ${url}`)
const githubInfo = await axios(url) // Chamada da API para obter as informações do usuário no Github.
return {
name: githubInfo.data.name,
bio: githubInfo.data.bio,
repos: githubInfo.data.public_repos
}
}
// Iterar por todos os usuários e retornar suas informações do Github.
const fetchUserInfo = async (names) => {
const requests = names.map((name) => {
const url = `https://api.github.com/users/${name}`
return fetchGithubInfo(url) // Função assíncrona que faz o fetching das informações do usuário.
.then((a) => {
return a // Retorna as informações do usuário.
})
})
return Promise.all(requests) // Aguardando até que todas as solicitações sejam resolvidas.
}
fetchUserInfo(['sindresorhus', 'yyx990803', 'gaearon'])
.then(a => console.log(JSON.stringify(a)))
/*
Resultado:
[{
"name": "Sindre Sorhus",
"bio": "Full-Time Open-Sourcerer ·· Maker ·· Into Swift and Node.js ",
"repos": 996
}, {
"name": "Evan You",
"bio": "Creator of @vuejs, previously @meteor & @google",
"repos": 151
}, {
"name": "Dan Abramov",
"bio": "Working on @reactjs. Co-author of Redux and Create React App. Building tools for humans.",
"repos": 232
}]
*/
Para concluir, Promise.all é a melhor maneira de agregar um grupo de promises em uma única promise. Essa é uma das maneiras de se obter concorrência em JavaScript.
Espero que tenha gostado deste artigo. Se gostou, bata palmas e compartilhe-o.
Se não gostou, tudo bem. Bata palmas e compartilhe do mesmo jeito. 😋