Promise async/await Javascript
Ainda tem dúvidas sobre callback, Promise e async/await? Este artigo detalha através de exemplos e explicações a utilização destes recursos.
Callback
JavaScript é uma linguagem single thread, então cada linha de comando só é executada quando a anterior é finalizada. Execute o comando abaixo no console do seu navegador e veja que o segundo console.log
só é executado depois que você clicar no "OK" do alert
:
console.log('Olá');
alert('Olha o seu console');
console.log('finalizado');
Não se preocupe com a presença desteundefined
no console, existe uma explicação mas isto será objeto de estudo em um outro momento.
Mas e se tivermos uma linha de código que demande mais tempo para ser executada? vamos travar toda a tela do usuário? Por sorte a engine do JavaScript nos possibilita ter um comportamento assíncrono e é justamente aí que o callback entra. Então antes de falar sobre Promise
vamos falar um pouco sobre callback.
O callback tem o propósito de continuar a execução depois que um processamento assíncrono é efetuado. Isto é extremamente útil, pois podemos executar tarefas em background e deixar configurado para que uma determinada função (callback) seja chamada ao término dessa execução.
Já reparou que o setTimeout
trabalha exatamente da forma comentada logo acima? Nós registramos uma função (callback) que é executada após um determinado tempo:
setTimeout(
() => alert('olá), // callback é executado após 2 segundos
2000 // 2 segundos
);
Vamos para um outro exemplo, uma requisição AJAX com uma biblioteca bem conhecida, jQuery:
function meuCallback(response) {
// Quando o $.ajax finalizar
// a requisição, este método
// será executado
// TODO faz alguma coisa
console.log(response);
}
$.ajax({
dataType: "json",
url: url,
data: data,
success: meuCallback // << callback
});
Perceba que na chamada do $.ajax
passamos uma função (meuCallback
) como parâmetro (success
). Esta é a função callback que será chamada quando o $.ajax
finalizar seu trabalho em background.
Vamos para um outro exemplo bem simples, considere o código abaixo:
function calcular(a, b, callback) {
setTimeout(
() => {
callback(a + b);
},
3000 /* 3 segundos */
);
}
// Podemos declarar a função de callback
// inline...
calcular(1, 2, (resultado) => {
console.log('O resultado é', resultado);
});
// ... ou podemos criar uma função e depois
// passar seu nome como parâmetro
function processarResultado(resultado) {
console.log('O resultado é', resultado);
}
calcular(1, 2, processarResultado);
A função calcular
recebe 3 parâmetros:
a
=> primeiro número a ser somadob
=> segundo número a ser somadocallback
=> função que será executada quandocalcular
finalizar sua execução que ocorre justamente no término dosetTimeout
, após 3 segundos.
Cole o código acima no console do seu navegador para ver o resultado.
Para finalizar os exemplos, veja abaixo a função readFile
do Node.js que também trabalha com callback:
import { readFile } from 'fs';
// 1 (ordem de execução)
readFile('/etc/passwd', /* callback */ (err, data) => {
// 3
if (err) {
// 4 - em caso de erro
throw err;
}
console.log(data);
// 4
});
// 2
O código acima efetua a leitura de um arquivo e os números cometandos acima representam a sequência de execução do código. Novamente temos um callback, que é o segundo parâmetro da função readFile
. Este callback é uma função que recebe dois parâmetros:
err
: detalhes do erro, caso ocorradata
: resultado da leitura do arquivo
No exemplo acima veja que o interpretador irá passar pelo readFile
iniciando sua execução em background e logo na sequência irá para a próxima linha de comando fora do readFile
, que é o ponto marcado com o // 2
. Ter essa noção de como será a ordem de execução do código é extremamente importante.
Agora que falamos de callback, vamos falar sobre as Promises.
Promise
Promise
é um objeto que representa um valor futuro. Podemos fazer uma analogia á uma compra online, ou seja, após você efetuar a compra você terá um objeto em algum momento no futuro caso tudo ocorra bem, mas algo pode dar errado no meio do caminho e você será informado.
Quando você instancia a Promise
, new Promise()
, ela está retornando uma "promessa" de que algo será concluído em breve. Para saber quando essa "promessa" será concluída, você deve registrar um callback, ou seja, você "diz" para a Promise
qual função deve ser executada quando ela terminar o processamento. A mesma regra vale para erros.
Então uma Promise
tem 3 possíveis estados:
- Pending - estado inicial, pendente de execução
- fulfilled - concluída com sucesso
- rejected - ocorreu algum erro
Então temos o seguinte:
const valorFuturo = new Promise(/* ... */);
valorFuturo
.then(valor => { /* ... */ }) // <-- callback de sucesso
.catch(erro => { /* ... */ }); // <-- callback de erro
Veja que o .then
e o .catch
são os métodos que a Promise
fornece para registrarmos um callback de sucesso ou erro respectivamente.
Exemplo setTimeout
Tomemos um outro exemplo com o setTimeout
:
function consultarDados() {
return new Promise((resolve) => {
setTimeout(
() => resolve('resolvida'),
2000 /* 2 segundos */
);
});
}
consultarDados().then(resultado => {
console.log(resultado);
});
console.log('olá');
A função consultarDados
retorna uma "promessa" (new Promise()
). Para obter o resultado da "promessa" nós registramos um callback através do método then
. Então quando consultarDados
finalizar sua execução automaticamente a função que passarmos dentro do then
será chamada e irá efetuar o console.log
do resultado recebido.
O resolve
dentro da Promise
é o "cara" que fala o seguinte: "terminei meu trabalho, tá aqui meus dados, pode chamar o callback do then()
". A mesma lógica vale para erros, porém ao invés de utilizarmos o then
vamos pegar os erros no catch
e indicar o erro dentro da Promise
com o reject
:
function consultarDados() {
return new Promise((resolve, reject) => {
setTimeout(
() => reject('ops ocorreu um erro'),
2000 /* 2 segundos */
);
});
}
consultarDados()
.then(resultado => {
console.log(resultado);
}).catch(erro => {
console.error(erro);
});
console.log('olá');
Veja que existe um padrão, sempre que trabalhamos com Promise
registramos um callback com o .then
para pegar o resultado do processamento em caso de sucesso e o .catch
para registrar um callback para pegar os detalhes do erro.
UnhandledPromiseRejectionWarning
Um ponto de atenção: se você registrar um callback apenas no .then
vai pegar a seguinte mensagem no console:
UnhandledPromiseRejectionWarning: Unhandled promise rejection (r ejection id: 1): Error: spawn cmd ENOENT
DeprecationWarning: Unhandled promise rejections are deprecated.
In the future, promise rejections that are not handled will terminate the Node.
Lembre-se de que uma Promise
pode ter dois resultados, sucesso ou erro, então sempre devemos tratar no código o .then
e o .catch
.
Exemplo readFile
Pegando carona no exemplo do readFile
do Node.js...
import { readFile } from 'fs';
readFile('/etc/passwd', (err, data) => {
if (err) {
throw err;
}
console.log(data);
});
...podemos encapsular o readFile
em uma função que retorne uma Promise
da seguinte forma:
import { readFile } from 'fs';
function lerArquivo() {
return new Promise((resolve, reject) => {
readFile('/etc/passwd', (err, data) => {
if (err) {
reject(err); // "fala" para o objeto Promise:
// "deu merda, tá aqui os detalhes do erro"
return;
}
resolve(data); // "fala" para o objeto Promise:
// "acabei, os dados são estes"
});
});
}
lerArquivo()
.then(data => console.log(data))
.catch(err => console.error(err));
Exemplo fetch
Para finalizar os exemplos vamos utilizar o fetch
para efetuar uma requisição AJAX:
const url =
'https://run.mocky.io/v3/dfd8147a-17ca-4b9c-8887-831c7d99e104';
fetch(url)
.then(function(response) {
response.json()
.then(function(json) {
console.log(json);
}).catch(
errJson => console.error(errJson)
);
}).catch(
err => console.error(err)
);
Cole o código acima no console do seu navegador e veja o resultado:
Repare que fetch()
retorna uma Promise
, então registramos um callback no .then()
. Depois que obtivermos o response
chamamos o método .json()
que também retorna uma Promise
e novamente registramos um outro callback no .then()
para obter o resultado final. Observe que temos duas Promise
, uma dentro da outra (promise chain). Isso nos leva ao próximo assunto.
Callback hell
Vamos supor que precisamos efetuar 3 operações assíncronas com cada operação dependendo do resultado da execução da operação anterior:
function iniciar() {
operacao1().then(resultado1 => {
operacao2(resultado1).then(resultado2 => {
operacao3(resultado2).then(resultado3 => {
// ****
// TODO
// ****
}).catch(erro3 => {
});
}).catch(erro2 => {
});
}).catch(erro1 => {
});
}
Perceba que temos uma função dentro da outra várias vezes. Quando chegamos neste cenário popularmente conhecido como Callback Hell, fica bem difícil de dar manutenção pela péssima legibilidade do código.
Promise - async/await
É justamente no cenário descrito acima que o async/await
pode ajudar. Se você estiver utilizando Promise
poderá utilizar este recurso. Veja como o código acima pode ser refatorado e depois entramos nos detalhes:
async function iniciar() {
try {
const operacao1 = await Operacao1();
const operacao2 = await Operacao2(operacao1);
const operacao3 = await Operacao3(operacao2);
} catch (err) {
}
}
O await
indica que o interpretador do JavaScript deve esperar a conclusão daquela Promise
para então seguir para a próxima linha. Veja também que a função iniciar()
tem a marcação async
indicando que dentro daquela função teremos a utilização do await
.
O código ficou muito mais legível e exatamente com a mesma funcionalidade. Vamos tomar novamente o exemplo do fetch
citado um pouco mais acima:
(async function() {
const url =
'https://run.mocky.io/v3/dfd8147a-17ca-4b9c-8887-831c7d99e104';
try {
const response = await fetch(url);
const json = await response.json();
console.log(json);
} catch (err) {
console.error(err);
}
})(); // => o () serve para executar a função -
// neste caso é uma função auto-executável
Novamente, o fetch
e o response.json()
retornam uma Promise
, então utilizamos o await
em cada uma das chamadas para esperar sua conclusão para então passar para o próximo comando.
A proposta das funçõesasync/await
é de simplificar o uso de forma síncrona dasPromises
e executar alguns procedimentos em um grupo dePromises
. Assim comoPromises
são similares acallbacks
estruturados, funçõesasync/await
são similares à junção degenerators
comPromises
.
fonte: https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Statements/async_function
Considerações
O entedimento claro da Promise
e da utilização do async/await
pode deixar seu código muito mais legível. Então sempre que se deparar com uma função que retorne uma Promise
você poderá considerar a utilização do async/await
. Destaco a importância do entedimento do fluxo de execução. Se você ficou com dúvidas, faça alguns testes no seu console para ajudar no entedimento.
Links interessantes:
- https://developer.mozilla.org/pt-BR/docs/Glossary/Callback_function
- https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Promise
- https://262.ecma-international.org/6.0/#sec-promise-objects
- https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Statements/async_function
- http://callbackhell.com