Asynchronous JavaScript: Callbacks, Promises e Async/Await

17/09/2024

1. O Que é Assincronismo?

Em JavaScript, operações assíncronas são aquelas que podem ser realizadas enquanto o restante do código continua a ser executado. Isso é útil para tarefas que podem demorar, como chamadas de APIs ou consultas a um banco de dados, permitindo que o programa continue rodando sem esperar que essas operações sejam concluídas.

O JavaScript usa o modelo de execução baseado em eventos, conhecido como "event loop", que lida com essas operações de forma não bloqueante. Através do event loop, o código assíncrono pode ser executado de forma eficiente e paralela a outras tarefas.

2. Callbacks

Antes do ECMAScript 6 (ES6), a maneira mais comum de lidar com código assíncrono em JavaScript era através de callbacks. Um callback é uma função passada como argumento para outra função, sendo chamada após a conclusão de uma operação.

Exemplo de Callbacks


// Função assíncrona com callback
function buscarDados(callback) {
    setTimeout(() => {
        console.log('Dados obtidos!');
        callback();
    }, 2000);
}

// Chamada da função com callback
buscarDados(() => {
    console.log('Processando os dados...');
});

No exemplo acima, a função buscarDados() simula uma operação assíncrona com setTimeout(), e o callback é executado após a conclusão dessa operação.

Problema: Callback Hell

O uso extensivo de callbacks pode levar ao que é conhecido como callback hell, onde múltiplos callbacks aninhados tornam o código difícil de ler e manter.


// Exemplo de callback hell
buscarDados(() => {
    processarDados(() => {
        salvarDados(() => {
            console.log('Dados salvos!');
        });
    });
});

Esse padrão de código aninhado torna-se complicado de gerenciar à medida que a complexidade aumenta.

3. Promises

Com o ES6, o JavaScript introduziu as promises, que melhoram a maneira de lidar com operações assíncronas, oferecendo uma solução mais clara e legível. Uma promise é um objeto que representa a eventual conclusão (ou falha) de uma operação assíncrona e seu valor resultante.

Estrutura de uma Promise


// Criando uma Promise
const promessa = new Promise((resolve, reject) => {
    setTimeout(() => {
        const sucesso = true;
        if (sucesso) {
            resolve('Operação concluída com sucesso!');
        } else {
            reject('Erro na operação.');
        }
    }, 2000);
});

// Consumindo a Promise
promessa.then((mensagem) => {
    console.log(mensagem);
}).catch((erro) => {
    console.error(erro);
});

As promises utilizam os métodos .then() para manipular o sucesso e .catch() para lidar com erros. Isso facilita o encadeamento de operações assíncronas de uma forma mais organizada.

Encadeamento de Promises

Uma das vantagens das promises é a capacidade de encadear múltiplas operações assíncronas sem cair no callback hell.


buscarDados().then(processarDados)
    .then(salvarDados)
    .then(() => {
        console.log('Todos os dados foram salvos.');
    }).catch((erro) => {
        console.error('Erro: ', erro);
    });

Isso torna o código muito mais legível, mesmo quando se trata de várias operações assíncronas em sequência.

4. Async/Await

No ES2017 (ES8), foi introduzida a sintaxe async/await, que é uma maneira ainda mais simples e intuitiva de lidar com promises. O async transforma uma função em uma função assíncrona que retorna uma promise, e o await pausa a execução da função até que a promise seja resolvida ou rejeitada.

Exemplo de Async/Await


// Função assíncrona usando async/await
async function executar() {
    try {
        const dados = await buscarDados();
        console.log('Dados obtidos:', dados);
        
        await processarDados(dados);
        await salvarDados(dados);
        
        console.log('Dados processados e salvos!');
    } catch (erro) {
        console.error('Erro: ', erro);
    }
}

executar();

Essa sintaxe torna o código assíncrono muito mais próximo do código síncrono, facilitando o entendimento e a manutenção. O try/catch é utilizado para capturar erros, tornando a manipulação de exceções também mais simples.

Vantagens do Async/Await

  • Código mais legível e limpo, semelhante ao síncrono.
  • Facilita o tratamento de erros com blocos try/catch.
  • Evita o encadeamento complexo de .then() e .catch().

5. Conclusão

O JavaScript oferece várias maneiras de lidar com operações assíncronas, desde callbacks até a sintaxe moderna async/await. Embora os callbacks tenham sido amplamente usados no passado, as promises e o async/await trazem uma abordagem mais poderosa e legível para o código assíncrono. Ao entender essas três abordagens, você estará mais preparado para lidar com tarefas assíncronas de forma eficiente em seus projetos JavaScript.