Promises é um conceito super importante em JavaScript que precisamos entender bem! Mas antes, temos que aprender sobre dois temas essenciais: programação síncrona e assíncrona.
Programação Síncrona
Síncrono é aquilo que ocorre simultaneamente. Da mesma forma, a programação síncrona é aquela que ocorre simultaneamente, ao mesmo tempo.
Isso significa que o código precisa estar em “sincronia” com ele mesmo, de modo a executar funções e receber respostas a essas funções logo em seguida. Então, podemos dizer que o código é executado “na ordem” esperada. Por exemplo:
function funcao1(){
console.log("Primeiro executa essa aqui...")
}
function funcao2(){
console.log("... depois essa daqui...")
}
function funcao3(){
console.log("...e por último essa!")
}
funcao1()
funcao2()
funcao3()
O resultado impresso é, como se espera:
Programação Assíncrona
Na programação assíncrona as funções não são executadas em ordem. Com o assincronismo podemos interromper do código para conseguirmos alguma outra informação necessária para a continuar a execução. Isso significa que o código espera por uma outra parte do código e, enquanto espera, executa as demais partes.
Para exemplificar esse efeito, sem usar Promises, podemos usar o método setTimeout() do JavaScript, dessa forma:
function funcao1(){
console.log("Vamos testar esse assincronismo!")
}
function funcao2(){
setTimeout((funcao1) => {
console.log("Funcionou, será?")
}, 2500);
}
funcao2()
Podemos perceber a execução assíncrona nesse caso quando executamos o código. No console observamos que o tempo de execução foi o que especificamos nos parâmetros dotimeOut(): aproximadamente 2.5s.
Dessa forma a String “Funcionou, será?” é impressa depois dos segundos que especificamos pelo timeOut(). Para visualizar melhor, podemos executar uma terceira função que não seja marcada pelo assincronismo e veremos que, mesmo depois, ela será impressa primeiro.
function funcao1(){
console.log("Vamos testar esse assincronismo!")
}
function funcao2(){
setTimeout((funcao1) => {
console.log("Funcionou, será?")
}, 2500);
}
function funcao3(){
console.log("Vou ser impressa primeiro pois não estou marcada por assincronismo!")
}
funcao2()
funcao3()
Como usamos o setTimeout junto de uma Arrow Function, a String que especificamos em funcao1() foi “esquecida”. Podemos modificar isso para exibir as três Strings dessa forma:
function funcao01(){
console.log("Vamos testar esse assincronismo!") //e por último essa daqui!
}
function funcao02(){
setTimeout(funcao01, 2500);
console.log("Funcionou, será?") //não é uma execução assíncrona, então imprime primeiro!
console.log("...")// depois essa...
}
funcao02()
O efeito “Callback Hell”
As Promises também são úteis para resolver o “callback hell”, que acontece quando usamos várias callbacks dentro de outras callbacks para termos retornos assíncronos de várias funções, deixando o código poluído e confuso.
Podemos visualizar isso melhor no exemplo abaixo:
setTimeout(function(){
console.log("Executando Callback...")
setTimeout(function (){
console.log("Executando Callback...")
setTimeout(function(){
console.log("Executando Callback...")
}, 2000)
}, 2000)
}, 2000)
Podemos ver como o código fica excessivo e complicado. Por outro lado, quando usamos Promises o resultado é bem mais simples:
function esperarPor(tempo = 2000){
return new Promise(function(resolve){
setTimeout(function(){
console.log("Executando Promise...")
resolve() //ao chamar o resolve, o then vai ser chamado
}, tempo)
})
}
esperarPor() //se não passar nenhum valor, ele espera o valor padrão de 2s
.then(() => esperarPor())
.then(esperarPor)
Entendendo Promises
Primeiramente, é importante saber que as Promises são objetos. Consequentemente, sempre que instanciarmos uma nova Promise, ela deverá estar acompanhada da palavra new.
Como vimos, usamos as Promises para processamento assíncrono. Elas recebem duas funções callback, que são funções que chamam outras funções: a resolve e a reject. Usamos essas funções, respectivamente, quando a Promise é resolvida e quando ela é rejeitada, ou dá erro. Qualquer uma das duas situações chama uma função correspondente.
Além disso, as Promises contam também com diversos métodos, sendo o foco aqui o método then() e o método catch(). O método then() possui dois argumentos, que também são funções callback resolve e reject. Enquanto o then() lida com ambos casos (tanto se a Promise for resolvida quanto rejeitada), o catch() lida apenas com casos de rejeição.
Por sempre retornarem Promises, podemos encadear tanto o then() quanto o catch(), embora geralmente usemos o catch() por último na linha de código, pois ele trata dos erros. Então depois de vários then’s, o catch vem para “coletar” todos os erros.
//Fazendo o tratamento de erro dentro da Promise
function funcionarOuNao(valor, chanceErro){
return new Promise((resolve, reject) =>{
if(Math.random() < chanceErro){//vai gerar um erro caso o numero sorteado seja menor que chanceErro
//o math.random vai fazer a randomização entre 0 e 1
reject('Ocorreu um erro!')
}else {
resolve(valor)
}
})
}
//antes de criar uma forma de tratar o erro, o erro é reconhecido pelo próprio Node, onde ele avisa que
//não há nenhum tratamento de erro
funcionarOuNao('Testando...', 0.9)
.then(v => `Valor: ${v}`)
.then(v => consol.log(v),
err => console.log(`Erro Específico: ${err}`)
)//como o erro foi tratado dentro de um then, o catch não será chamado nesse caso
.catch(err => console.log(`Erro: ${err}`))//tratamento do erro!
//depois do catch nenhum valor é obtido, por isso ele é, em geral, colocado no fim do código para pegar
//os erros de todos os fluxos
.then(() => console.log("Fim!"))
Por hoje é isso, pessoal! Para aprender mais sobre outros conceitos em JavaScript, dá uma olhada em nossos posts anteriores sobre o tema.
Bons estudos!