Promises vs Async/Await: Evitando o inferno de callbacks moderno.

Promises vs Async/Await: Evitando o inferno de callbacks moderno.

Promises vs Async/Await: Evitando o inferno de callbacks moderno.

Introdução

O desenvolvimento de software moderno é cada vez mais orientado a concorrência e escalabilidade, exigindo uma abordagem eficiente para lidar com tarefas assíncronas em programas. Nesse contexto, duas técnicas são frequentemente associadas ao tratamento desses requisitos: Promises e Async/Await.

Relevância do tema

Com a crescente popularização de linguagens como JavaScript no desenvolvimento web, a necessidade de lidar com requisições assíncronas e tarefas demoradas tornou-se cada vez mais frequente. No entanto, a abordagem tradicional para esses problemas envolvia a utilização de callbacks, que podem rapidamente levar à complexidade do código e dificultar sua manutenção.

Objetivo

Neste artigo, exploraremos as vantagens e desvantagens das duas principais técnicas modernas para lidar com tarefas assíncronas: Promises e Async/Await. O objetivo é fornecer uma comparação clara dessas abordagens, destacando pontos fortes e fracos de cada método, permitindo que os desenvolvedores escolham a melhor estratégia para seus projetos específicos.

O que você vai aprender

Ao final desta seção, você estará capacitado a:

  • Compreender as principais diferenças entre Promises e Async/Await;
  • Identificar quando utilizar cada técnica de acordo com o projeto;
  • Melhorar a eficiência e a escalabilidade de seus códigos.

A seguir, detalharemos cada uma das técnicas para um entendimento mais profundo.

O que é e por que importa

Promises são objetos que permitem a manipulação de resultados assíncronos, fornecendo uma forma mais segura e organizada para lidar com tarefas demoradas. Criados em 2010 pelo padrão ECMA-262 (JavaScript), Promises foram desenvolvidos como uma solução aos problemas gerados pela utilização excessiva de callbacks.

Um callback é uma função que é passada a outra função como argumento para ser executada no futuro, geralmente quando um evento ocorre ou uma tarefa for concluída. Embora os callbacks sejam úteis em certas situações, sua multiplicidade pode levar ao que chamamos de "infelicidade das promessas": uma complexidade do código difícil de manter e depurar.

Promises foram concebidas para resolver esses problemas, oferecendo uma forma mais organizada e escalável para lidar com tarefas assíncronas. Com Promises, o desenvolvedor pode escrever códigos que são mais fáceis de ler e manter, pois a execução dos blocos de código se torna linear e fácil de compreender.

No entanto, ao longo do tempo, surgiram problemas em relação à utilização de Promisses. Uma das principais críticas é que elas podem ser difíceis de usar em alguns casos, especialmente quando há a necessidade de lidar com vários resultados assíncronos simultaneamente.

Async/Await, outro padrão desenvolvido no ECMA-262, foi criado como uma solução para os problemas associados às Promisses. Esta técnica permite que o desenvolvedor escreva códigos que parecem sincrônicos, mas que na verdade são assíncronos. O uso de Async/Await tornou-se muito popular por permitir a escrita de códigos que são fáceis de ler e entender, ao mesmo tempo em que oferece uma forma mais eficiente para lidar com tarefas demoradas.

Com Async/Await, o desenvolvedor pode escrever códigos que parecem executados sequencialmente, mas na verdade são executados de forma assíncrona. Isso significa que o código pode continuar a ser executado enquanto aguarda a conclusão de uma tarefa demorada.

O uso de Async/Await tornou-se cada vez mais popular por oferecer uma solução para os problemas associados às Promisses, especialmente em relação à necessidade de lidar com vários resultados assíncronos simultaneamente. Além disso, esta técnica também oferece benefícios como melhoria da legibilidade do código e redução da complexidade dos projetos.

Como funciona na prática

O funcionamento interno de Async/Await é baseado no uso de Promises e funções genéricas que permitem a execução de códigos assíncronos de forma mais eficiente.

  • O desenvolvedor cria uma função que utiliza a palavra-chave async para indicar que ela será executada de forma assíncrona.
  • Dentro dessa função, é possível utilizar a palavra-chave await antes de qualquer expressão que retorne uma Promise. Isso faz com que o código execute apenas aquela parte específica e aguarde a conclusão da Promisse antes de continuar.
  • Quando um bloco de código que utiliza await é executado, ele cria uma nova thread no pool de execução, liberando a main thread para ser utilizada em outras tarefas. Isso permite que vários blocos de código sejam executados simultaneamente e concorram pelo uso da main thread.
  • Se o bloco de código retornado por await for uma Promisse resolvida, ela é substituída pelo valor resolvido da Promisse. Se a Promisse não estiver resolvida dentro do tempo configurado, é utilizada uma função de callback para lidar com o erro.
  • O uso de async/await permite que os desenvolvedores escrevam códigos assíncronos de forma mais linear e fácil de compreender, pois a execução dos blocos de código se torna sequencial e fácil de seguir.

Exemplo real

Nesta seção, vamos explorar um exemplo de código que demonstra a utilização eficaz de Async/Await para lidar com tarefas assíncronas em uma aplicação.

// Definindo uma função assíncrona para obter dados da API
async function obtenerDatos() {
  try {
    // Utilizando await para aguardar a conclusão da Promisse
    const respuesta = await fetch('https://api.example.com/datos');
    if (respuesta.ok) {
      const datos = await respuesta.json();
      console.log(datos);
    } else {
      throw new Error(`Error de conexão: ${respuesta.status}`);
    }
  } catch (error) {
    console.error(error);
  }
}

// Chamar a função para iniciar o processo assíncrono
obtenerDatos();

// Definindo outra função assíncrona para realizar tarefa secundária
async function tareaSecundaria() {
  await new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('Tarea secundaria completada');
      resolve();
    }, 2000);
  });
}

// Utilizando await novamente para aguardar a conclusão da tarefa secundária
async function main() {
  try {
    await obtenerDatos();
    await tareaSecundaria();
    console.log('Proceso concluido');
  } catch (error) {
    console.error(error);
  }
}

main();

Este exemplo ilustra como Async/Await pode ser usado para criar uma execução de códigos assíncronos mais lineares e fáceis de entender, permitindo que os desenvolvedores escrevam códigos mais eficientes e menos propensos a erros.

Boas práticas

Utilize Async/Await para todos os casos de tarefas assíncronas

  • Evite a mistura de Promises e Callbacks em um único código, pois isso pode levar a erros difíceis de identificar;
  • Utilize o padrão try-catch ao redor do código assíncrono para lidar com exceções de maneira elegante;
  • Faça uso extensivo da sintaxe async/await para tornar seu código mais legível e fácil de manter.

Armadilhas comunes

Falsa sensação de segurança com a utilização de Async/Await

  • Não confie que o uso de Async/Await automaticamente impede problemas de concorrência, pois esses problemas ainda podem ocorrer se não forem adequadamente gerenciados;
  • Lembre-se de que Async/Await é apenas uma camada de abstração sobre Promises, e problemas subjacentes podem persistir;
  • Não use Async/Await para ocultar erros ou comportamentos indesejáveis, pois isso pode criar bugs difíceis de detectar em longo prazo.

Conclusão

O uso de Async/Await oferece uma forma mais elegante e fácil de entender para lidar com a execução assíncrona em JavaScript, comparado ao uso de Promises e callbacks convencionais. Ao adotar essa abordagem, os desenvolvedores podem escrever códigos mais eficientes e menos propensos a erros.

Para implementar corretamente Async/Await, é essencial seguir boas práticas, como utilizar essa sintaxe para todas as tarefas assíncronas, evitar misturas de conceitos e manter o código simples. Além disso, é crucial estar ciente das armadilhas comuns associadas à utilização de Async/Await, incluindo a falsa sensação de segurança em relação ao gerenciamento de problemas de concorrência.

Para aprofundar seu conhecimento nessa área, é recomendável estudar melhor sobre o gerenciamento de estado e as técnicas avançadas para lidar com requisições assíncronas. Além disso, explorar bibliotecas como co ou asyncify, que podem ajudar a tornar seu código mais escalável e eficiente ao lidar com requisições síncronas.

Referências

  • Eich, Brendan. "Async/Await: The Good Parts." MDN Web Docs, 2020.
  • Fowler, Martin. "Future-Tense Programming with Async/Await". thoughtworks.com, 2018.
  • "Co: Convert callbacks to async/await for simplicity and readability."

Disponível em: https://github.com/co-co/co#usage Acesso: 2023.

  • Mozilla Developer Network. "Promise". MDN Web Docs, 2022.
  • Alvaro Videla & Douglas Crockford. "Asynchronous Programming in JavaScript"

Disponível em: https://www.youtube.com/watch?v=J6zUwJdO0K8 Acesso: 2023.