Javascript assíncrono: Promises
Esse post é a primeira parte da série Javascript Assíncrono, no próximo posts vamos falar sobre async/await.
Com o avanço das tecnologias frontend e principalmente do Javascript, a programação assíncrona se tornou cada vez mais necessária e comum nas aplicações, principalmente em SPA’s. Desde os primórdios do jQuery possuímos as requisições XMLHttpRequest
(AJAX) que faziam um trabalho assíncrono.
Se você programa há um bom tempo sabe que a primeira forma de criar um código assíncrono no Javascript era utilizando os callbacks da seguinte forma:
function asyncFunction(params, callback, err) {
var response = // requisição assíncrona
if (response.status === true) {
callback(response.data);
} else {
err(response.data);
}
}
asyncFunction(
params,
function(response) {
console.log('Sucesso!');
},
function(err) {
console.log('Erro!');
}
);
A boa notícia é que os tempos mudaram e com os avanços da ECMAScript (padrões estipulados para utilização do Javascript) podemos escrever código assíncrono de forma muito menos verbosa e mais funcional.
Promises
Presente de forma nativa no Javascript desde 2015 no ES6, as Promises são classes que são utilizadas pela maioria das bibliotecas de requisições HTTP atualmente como Axios, jQuery, API Fetch, entre outras. A Promise é facilmente identificada por códigos que utilizam o .then
para callback de sucesso e .catch
para erros. Vamos ver como criar uma Promise:
const divDelayed = (a, b) => new Promise((resolve, reject) => {
setTimeout(() => {
if (b == 0) reject('O valor de B não pode ser zero!');
resolve(a / b);
}, 2000);
});
No exemplo acima estou criando uma função que recebe dois parâmetros, o retorno dessa função é uma nova Promise que sempre recebe como parâmetro uma função com dois parâmetros, resolve
e reject
.
Oresolve
é chamado quando temos uma resposta de sucesso ativando o.then
, e oreject
é chamado quando temos um erro disparando o.catch
.
Na função adicionei um setTimeout
para torná-la assíncrona e devolver o valor da divisão ou erro após dois segundos. No caso, se o valor do divisor for zero, um erro é disparado (reject), em qualquer outro caso a divisão é realizada e retornada pelo resolve
.
Utilizando a função acima em nosso código com divisor zero teríamos:
divDelayed(10, 0)
.then(resp => console.log(`Sucesso: ${resp}`))
.catch(err => console.log(err));
// O valor de B não pode ser zero!
Veja que apenas o .catch
foi disparado pois nossa Promise caiu no reject
e todo esse processo só irá acontecer após os dois segundos estipulados no setTimeout
.
Agora se utilizarmos com um divisor maior que zero:
divDelayed(10, 5)
.then(resp => console.log(`Sucesso: ${resp}`))
.catch(err => console.log(err));
// Sucesso: 2
O .then
é devidamente disparado com a resposta da divisão.
Encadeamento
Em alguns casos teremos uma Promise que retorna outra Promise e para acessar o resultado dessa segunda iremos precisar encadear as chamadas:
promise1()
.then(resp => resp.promise2())
.then(resp2 => console.log(resp2));
Nesse caso, retornamos a chamada da segunda Promise dentro do .then
da primeira, e encadeamos outro .then
logo abaixo para tratar o resultado da segunda.
Cascata
Por estarmos tratando de código assíncrono você provavelmente irá utilizar Promises em casos que você precisa esperar pelo resultado para realizar alguma ação e novamente, essa ação que está aguardando pode ser uma Promise, confuso? Vamos ao código. Imaginemos que estamos buscando os dados de um usuário pelo seu username e a partir disso queremos buscar todos seus endereços cadastrados, veja, duas chamadas, porém a segunda depende do sucesso da primeira (nesse caso o api.get
será nossa Promise de busca na API):
api.get('/users/diego3g').then(user => {
api.get(`/addresses/${user.id}`).then(addresses => {
console.log(addresses);
});
});
Essa organização é bonita até certo ponto, a partir do momento que precisamos começar a stackar muitas Promises o código vai se tornando muito verboso:
api.get('/users/diego3g').then(user => {
api.get(`/groups/${user.id}`).then(groups => {
groups.map(group => {
api.get(`/group/${group.id}`).then(groupInfo => {
console.log(groupInfo);
});
})
});
});
Nesse emaranhado de .then
dificilmente conseguimos bater o olho e enxergar o que é o que e por isso no post sobre async/await dessa série vou te explicar como organizar melhor tudo isso.
Promises simultâneas
Em alguns casos você necessita aguardar o resultado de múltiplas Promises porém nenhuma depende da outra para executar, nesse caso você pode utilizar o método Promise.all
para aguardar por todas elas:
Promise.all([
api.get('/users/diego3g'),
api.get('/phones/diego3g'),
]).then([user, phones] => {
console.log(user);
console.log(phones);
});
Passamos um vetor de Promises ao método e apenas um .then
recuperando o valor de cada Promise separadamente utilizando desestruturação de array do ES6, nesse caso temos o valor dos dados do usuário e telefones separados.
Corrida
Uma das funcionalidades desconhecidas porém muito útil da Promise é o método race
que nos permite criar uma corrida entre múltiplas Promises recuperando o resultado da que for mais rápido. Essa corrida é muito útil para adicionar timeout
à requisições assíncronas:
const timeout = ms => new Promise((resolve, reject) =>
setTimeout(reject, ms, { timeout: true });
);
Promise.race([api.get('/users/diego3g'), timeout(2000)])
.then(resp => console.log(resp))
.catch(err => console.log(err));
No caso acima, criamos uma função timeout
que apenas retorna um reject
após tanto tempo e iniciamos uma corrida entre uma Promise de busca de dados do usuário e a Promise de timeout com dois segundos. Ou seja, se a Promise demorar mais que dois segundos, o retorno no .catch
será: { timeout: true }
, agora se demorar menos que isso, os dados do usuário devem ser retornados no .then
.
Concluindo
Nesse post vimos como utilizar as Promises a nosso favor e organizar as requisições múltiplas quando dependemos ou não do resultado das Promises anteriores utilizando o modelo de cascata e o Promise.all
.
No próximo post dessa série vamos ver sobre async/await.
Se você gostou do post, não esquece de deixar suas ?