02 de novembro de 2022 • 7 min de leitura
JavaScript Promises
Entenda o que são promises, async/await no JavaScript.
O que é Promises ?
Um recurso utilizado para que seja executada uma sequência de comandos de forma assíncrona, ou seja, permite executar duas partes do código no mesmo tempo.
Sendo assim é possivel ter diversas vantagens, por exemplo manipular o código de maneira mais eficiente, manipular dados nas chamadas de APIs e tratar erros de forma mais limpa.
Veja a seguir um exemplo de uma sequência de comandos síncrona (tradicional), abaixo uma função que soma dois números e retorna uma outra função com o resultado:
function sumNumbers() {
let result = 1 + 1;
if (result === 2) {
successCallback();
} else {
errorCallback();
}
}
function successCallback() {
console.log("Resultado da soma é 2");
}
function errorCallback() {
console.log("Oops! Algo deu errado.");
}
sumNumbers();
// output: Resultado da soma é 2
Agora vamos refazer o código anterior para que desta vez ele retorne uma Promise. Esta Promise simplificará o uso dos callbacks
, veja só.
Para isso, se cria um new Promise
, que é uma classe que retornará um objeto. Na criação desta classe passaremos como parâmetro uma função anônima, que por sua vez receberá os parâmetros resolve
e reject
.
Ficou confuso? Calma que no exemplo ficará mais claro, mas por enquanto atente-se a esses parâmetros resolve
e reject
. O que esses parâmetros querem dizer? Querem dizer que se o código correr da maneira como deveria, ele executará o trecho resolve
do código. Caso algum erro aconteça, ele executará o trecho reject
.
Esses caras resolve
e reject
são o que chamamos de callbacks
.
let p = new Promise((resolve, reject) => {
let a = 1 + 1;
if (a == 2) {
resolve('Success');
} else {
reject('Failed');
}
});
Por enquanto entramos somente na parte da criação da Promise, agora vamos chamá-la. com o .then
, podemos forçar que o código só prossiga com a execução da função após que uma anterior seja resolvida. Veremos isso mais à frente, mas por enquanto se atente a forma como fazemos a chamada da Promise:
p.then((message) => {
console.log('Esse é o then: ' + message);
}).catch((err) => {
console.log('Esse é o catch: ' + err);
});
// output: Esse é o then: Success
// Caso mudássemos a condicional a == 2 para qualquer outro valor, provavelmente o output seria o do catch:
// output: Esse é o catch: Failed
Criando Promises
Por enquanto vimos os conceitos básicos de promise como por exemplo usá-las através do .then
e .catch
, mas vamos adicionar uma pitada de complexidade e tentar ir para um exemplo mais robusto de como fazer com que qualquer função retorne uma Promise.
Neste caso temos duas funções callbacks síncronas chamadas errorCallback
e callback
. Elas retornarão um objeto com a propriedade name
e message
. Uma função comum com sua chamada seria algo assim:
const myName = 'Jefferson';
function whoAreYouCallback(callback, errorCallback) {
if (myName != 'Jefferson') {
errorCallback({
name: 'Algo deu errado',
message: myName + ' não é meu nome.'
})
} else {
callback({
name: myName,
message: 'Olá, mundo!'
});
}
}
whoAreYouCallback((result) => {
console.log(result.name + "? Sou eu! " + result.message);
}, (error) => {
console.log(error.name + '. ' + error.message);
})
// output: Jefferson? Sou eu! Olá, mundo!
Agora para retornar uma promise, você verá que não precisaremos adicionar os errorCallback
e callback
nos parâmetros da função, apenas faremos com que essa função retorne uma Promise diretamente.
Para retornar uma promise, adicionamos o return new Promise
seguido de uma arrow function
(função anônima) com as callbacks de parâmetro da função anônima.
Para a chamada de Promise, adicionamentos o .then
para tratar o cenário de sucesso e o .catch
para o cenário de falha:
const myName = 'Alan';
function whoAreYouCallback() {
return new Promise((resolve, reject) => {
if (myName != 'Alan') {
reject({
name: 'Algo deu errado',
message: myName + ' não é meu nome.'
})
} else {
resolve({
name: myName,
message: 'Olá, mundo!'
});
}
})
}
whoAreYouCallback()
.then((result) => {
console.log(result.name + "? Sou eu! " + result.message);
}).catch((error) => {
console.log(error.name + '. ' + error.message);
})
// output: Alan? Sou eu! Olá, mundo!
Determinando a ordem de chamada das funções
Agora que sabemos usar as Promises, podemos ativar o seu superpoder: Fazer com que o código prossiga somente após a execução de uma determinada promise e permitindo que você controle a ordem e quando fará a execução do seu código. Neste sentido, basta aninharmos o próximo .then
sendo passado como parâmetro no resultado da promise anterior.
Neste exemplo vamos criar duas promises e chamaremos através do método tradicional .then
e .catch
:
// Criando a primeira promise
function bestF1DriverEver(driver) {
return new Promise((resolve, reject) => {
if (driver === 'Senna') {
resolve ({
success: true,
driverName: 'Ayrton Senna',
msg: driver + ' é o melhor piloto de F1 de todos os tempos!'
});
} else {
reject ({
success: false,
msg: 'Esse não é o melhor piloto!'
});
}
});
}
// Criando a segunda promise
function bestF1Car(response) {
return new Promise((resolve, reject) => {
if (response.success) {
resolve('McLaren MP4/4 pilotada por ' + response.driverName);
} else {
reject('Resposta errada! Tente de novo')
}
});
}
// Chamando uma promise e depois a outra
bestF1DriverEver('Senna')
.then(response => {
console.log('Verificando resposta...');
return bestF1Car(response);
})
.then(response => {
console.log('Encontrando o melhor carro...');
console.log(response);
})
.catch(err => {
console.log(err.msg);
})
// output: Verificando resposta...
// output: Encontrando o melhor carro...
// output: McLaren MP4/4 pilotada por Ayrton Senna
Se caso quiséssemos criar mais chamadas consecutivas de promises para executar o código em uma determinada ordem somente após o término da execução de uma outra função anterior, basta adicionarmos mais .then
na chamada retornando a promise anteriormente usada, como foi o caso da return bestF1Car(response)
.
No exemplo anterior, vimos que a segunda promise confere se o resultado da primeira promise foi um sucesso. Caso positivo, ela também seguirá no cenário de sucesso.
Porém, essa chamada de promise acaba gerando um problema: A partir da chamada de mais de uma promise, vai-se criando um aninhamento de .then
retornando a próxima promise. Em uma chamada de muitas promises, isso pode tornar o código complexo e ilegível.
Async / Await
Para resolver o problema da complexidade de aninhamentos de .then
, o async / await
vem para simplificar o trabalho de chamada de promises.
O primeiro passo é explicitar que a função será assíncrona adicionando o prefixo async
na chamada das promises, para então chamar as promises desejadas na ordem que desejada de execução utilizando o prefixo await
.
Com as duas promises criadas no exemplo anterior, poderíamos chamá-las dessa forma:
async function runPromises() {
const bestF1DriverResponse = await bestF1DriverEver('Senna');
console.log(bestF1DriverResponse);
const bestF1CarResponse = await bestF1Car(bestF1DriverResponse);
console.log(bestF1CarResponse);
}
runPromises()
/*
output: {
"success": true,
"driverName": "Ayrton Senna",
"msg": "Senna é o melhor piloto de F1 de todos os tempos!"
}
output: McLaren MP4/4 pilotada por Ayrton Senna
*/
E caso o parâmetro passado faça cair no erro .catch
da promise? Neste caso, é necessário envelopar nossas chamadas dentro de um bloco try / catch
para fazer o tratamento do erro.
async function runPromises() {
try {
// chamando a promise com o parâmetro errado
const bestF1DriverResponse = await bestF1DriverEver('Piquet');
console.log(bestF1DriverResponse);
const bestF1CarResponse = await bestF1Car(bestF1DriverResponse);
console.log(bestF1CarResponse);
} catch (err) {
console.log(err.msg);
// Como o parâmetro passado foi errado, ele cairá neste bloco catch
}
}
runPromises()
// output: Esse não é o melhor piloto!
O uso do async / await
junto com try / catch
é uma grande vantagem pois o try / catch
irá capturar erros de promise no reject
como também pode capturar outros erros também.
Guia Rápido
O que é uma promise
?
É um objeto que "encapsula" o estado de uma execução (sucesso ou falha) e executa callbacks
com base neste estado.
Qual é a relação entre Promise
e callback
?
As callbacks
são parâmetros dentro da função de uma promise que são responsáveis para designar como a promise irá se comportar dependendo do seu resultado. As duas principais callbacks
usadas são resolve
e reject
.
Resolve
: Se a promise funcionar como esperada, essa callback irá retornar a sequência de resolução do código.Reject
: Se a promise não funcionar como esperada, essa callback irá retornar um erro para a chamada da promise.
Qual é a relação entre Promise e Async / Await
?
Async / Await
é uma forma mais simples de se chamar promises. Para usá-los, nós precisamos ter uma promise criada, uma função com o prefixo async
onde será inserida as chamadas da promise com o prefixo await
.
O que Async / Await
faz com nosso código?
Irá executar o bloco .then
sem a necessidade de aninhá-los para cada chamada de promise, tornando o código mais legível. Além disso, para tratar erros apropriadamente, é importante que envolva as chamadas da promises await
em torno de um bloco try / catch
.
Existe algo a mais para saber sobre promises?
Sim, recentemente o Node.JS
teve um update com o Promise.all
para chamar várias promises em ainda menos linhas de código.
Ah, e nunca se esqueça que você pode sempre contar com o MDN Docs para aprender de uma fonte confiável!