Tarefas Assíncronas com Promises em JavaScript
Introdução
Esse comportamento costumava ser tratado através de callbacks, no entanto, com o tempo isso se tornou tedioso de manter e a indentação do código se tornou cada vez mais problemática quanto mais validações e processos derivados precisavam ser realizados. Isso deu lugar à introdução das Promises no JavaScript.
As Promises do JavaScript foram introduzidas com o ECMAScript 6 e trouxeram para a linguagem uma forma mais concisa de trabalhar com funções assíncronas.
É importante notar que as Promises não são suportadas pelo Internet Explorer. Para obter esse comportamento é necessário usar polyfills.
Funções assíncronas incluem ações como leituras de arquivos, requisições a servidores (ajax), funções que esperam um certo período de tempo (como setTimeout
), processos que podem ser intensivos para a CPU, como o hashing de informações, entre outros.
Callbacks
Callbacks são funções enviadas como parâmetros para outra função. Espera-se que a última execute o código recebido após algum tempo.
No caso da função setTimeout
que usaremos como exemplo, ela recebe uma função como parâmetro que é executada assim que o período definido em milissegundos termina.
let asincrono = function (cb) {
setTimeout(function () {
cb('segundo');
}, 3000);
};
console.log('primeiro');
asincrono(function (result) {
console.log(result);
});
console.log('terceiro');
O resultado no console seria:
primeiro;
terceiro;
segundo; // após 3 segundos
Vemos que a impressão de "segundo" não acontece até que um tempo tenha passado (neste caso, o especificado em setTimeout
de 3000
ms).
Aqui usamos uma função chamada cb
(ela pode ter qualquer nome), para ser chamada uma vez que o processo assíncrono tenha terminado, neste caso, um intervalo de tempo de 3 segundos.
Promise()
As Promises do JavaScript recebem uma função com dois parâmetros (resolve
, reject
) que são funções chamadas uma vez que o processo é executado com sucesso (resolve
) ou falha (reject
).
As Promises têm três estados:
- Pending (esperando para ser resolvida ou rejeitada)
- Fulfilled (resolvida)
- Rejected (rejeitada)
Reescrevendo o exemplo anterior com promises ficaria assim:
// promises são instanciadas com 'new'
let asincrono = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('segundo');
}, 3000);
});
console.log('primeiro');
asincrono.then(function (result) {
console.log(result);
});
console.log('terceiro');
A saída do console seria a mesma do exemplo anterior.
Outro uso das promises é poder saber quando uma consulta assíncrona, por exemplo, a um servidor, está concluída.
let datosDeServidor = function (url) {
return new Promise(function (resolve, reject) {
let xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function () {
resolve(xhr.responseText); // acessado com 'then()'
};
xhr.onerror = function () {
reject(xhr.statusText); // acessado com 'catch()'
};
xhr.send();
});
};
datosDeServidor(/* url */)
.then(function (response) {
// consulta concluída
console.log(response); // xhr.responseText
})
.catch(function (error) {
// erro na requisição
console.log(error); // xhr.statusText
});
Vemos que then()
é usado para resolve
e catch()
para reject
.
Muitas das bibliotecas usadas em JavaScript e NodeJs suportam o uso de Promises. Por exemplo, axios
, usado para fazer requisições ajax, é baseado em Promises, então seu uso geral é:
axios({
/* options */
})
.then(function (response) {
// requisição bem-sucedida
console.log(response);
})
.catch(function (error) {
// falha na requisição
console.log(error);
});
Promise.all()
Promise.all()
recebe um array de promises e retorna um array com as promises uma vez que elas tenham sido resolvidas. Usando como exemplo a consulta a um servidor, poderíamos usar Promise.all()
se tivermos várias consultas assíncronas nas quais queremos agir assim que todas terminarem.
Promise.all([
datosDeServidor(/* url do servidor x */),
datosDeServidor(/* url do servidor y */),
datosDeServidor(/* url do servidor z */),
])
.then(function (results) {
console.log(results[0]); // resposta do servidor x
console.log(results[1]); // resposta do servidor y
console.log(results[2]); // resposta do servidor z
})
.catch(function (error) {
console.log(error); // erro da primeira promise que falhar
});
As Promises do JavaScript nos dão maior flexibilidade ao lidar com código assíncrono. Um dos usos mais comuns das promises no desenvolvimento web é poder controlar o fluxo de requisições (ajax) a servidores web, já que, dependendo da conexão e outros fatores, o tempo exato que leva para uma resposta é imprevisível.