Programação assíncrona com Promises

O Javascript ES6 introduziu o conceito de Pomisses para pedidos assíncronos feitos a um servidor.

Antes de haver Promisses, as respostas com dados do servidor eram recebidas numa função de callback. Tal, no entanto, poderia levar a situações confusas que as Promisses vieram obviar.

O que é uma Promisse ? Uma Promisse é um contentor para um valor que chegará no futuro. É uma promessa, tal como na vida real.

Podemos criar Promisses e/ou consumir Promisses.

Modo de funcionamento das Promises

Primeiro que tudo, uma Promisse é um objeto. Uma Promisse possui 3 estados:

  • Pending: estado inicial da Promisse antes de ser bem sucedida ou falhar
  • Resolved: Promisse completa e bem sucedida
  • Rejected: Promisse falhada

Quando solicitamos dados do servidor usando uma Promisse, ele ficará no estado pendente (pending) até recebermos os dados.

Se conseguirmos obter as informações do servidor, a Promisse será resolvida com sucesso e passará ao estado resolved. Mas se não obtivermos as informações, a Promisse ficará no estado rejeitado (rejected).

Criação de Promises

Para construirmos uma Promise, e dado que uma Promise é um objeto, usamos o construtor da Promise. Este construtor aceita como argumento uma função com dois parâmetros:

resolve e reject que são referências para funções .

const myPromise = new Promise((resolve, reject) => { 
   let condition; 

   if(condition is true) { 
      resolve('Promise is resolved successfully.');    
   } else { 
      reject('Promise is rejected'); 
   } 
});

Na Promise, excutamos uma tarefa qualquer. Se a tarefa f0r bem sucedida, chamamos a função resolve(). Por exemplo, se a tarefa for obter dados do servidor, e for bem sucedida, chamamos a função resolve() passando-lhe os dados. Se a tarefa não for bem sucedida chamamos a função reject() com um código de erro ou uma mensagem de erro.

Consumo de Promises

Uma Promise é um objeto. Esse objeto possui os métodos: then() e catch().

O método then() é chamado quando, na origem, a Promise é bem sucedida e passa para o estado resolved.

O método catch() é chamado quando, na origem, a Promise é mal sucedida e passa para o estado rejected.

Se acedermos a um serviço que retorna uma Promise, basicamente, temos de tomar ações nesses dois métodos.

Exemplo:

const promise = service.findAll();

promise.then((dados) => { 
   let html = ""; 
   dados.forEach( 
      (dado) => (html += `<tr><td>${dado.name}</td><td>${rate.years}</td><td>${dado.rate}%       </td></tr>`) 
); 
      document.getElementById("dados").innerHTML = html; 
}) 
.catch((e) => console.log(e)); 

Neste exemplo, acedemos ao método findAll() de um qualquer serviço na web, para obter dados. O método findAll(), retorna uma Promise. Se a Promise tiver sido bem sucedida, ou seja, estiver no estado resolved, o método then() da Promise será chamado e, nesse método, mostramos os dados na página. Se a Promise não tiver sido bem sucedida, ou seja, estiver o estado rejected, o método catch() da Promise será chamado com um erro, e mostramos esse erro na consola.

Async/Await

O Javascript ES6 introduziu uma melhoria sintática significativa no consumo de Promises. Trata-se das instruções Async/Await. O Async/Await permite-nos escrever código assíncrono como se estivéssemos a escrever código sincrono.

Async/Await funciona da seguinte maneira: temos uma função de javascript na qual iremos fazer um pedido assíncrono para obtermos dados numa Promise. Essa função é decorada com a palavra reservada async.

Quando fazemos o pedido, dado que é um pedido assíncrono, os dados serão obtidos algures no futuro. Enquanto os dados não são recebidos, a palavra reservada await faz com que o fluxo do programa bloqueie nesse ponto, até os dados chegarem. Quando os dados chegam, são recebidos numa variável ou constante.

Devemos reter o seguinte:
Sempre que usamos uma instrução com await, essa instrução deve estar numa função decorada com async !

De notar que quando consumimos uma Promise com async/await devemos envolver o código na função async num bloco try/catch. O código sai pelo bloco catch caso a Promise seja rejeitada.

Um exemplo simples em JavaScript de consumo de Promises usando async/await seria algo assim:

// Função que simula uma operação assíncrona retornando uma Promise
function obterDados() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const sucesso = true; // altere para false para simular erro
if (sucesso) {
resolve("Dados recebidos com sucesso!");
} else {
reject("Houve um erro na obtenção dos dados.");
}
}, 2000); // simula atraso de 2 segundos
});
}

// Função assíncrona que consome a Promise usando async/await
async function pesquisarDados() {
try {
console.log("A obter dados...");
const resultado = await obterDados(); // aguarda a Promise
console.log(resultado);
} catch (erro) {
console.error("Erro:", erro);
}
}

// Executa a função
pesquisarDados();

O código:

  • Cria uma função obterDados que retorna uma Promise.
  • A função pesquisarDados utiliza async/await para esperar o resultado da promessa.
  • O bloco try/catch trata erros de forma semelhante ao código síncrono.