13 ‐ Entendendo as diferentes formas de lidar com o assincronismo - sabrinabm94/javascript GitHub Wiki

13 ‐ Entendendo as diferentes formas de lidar com o assincronismo

Aqui vamos aprender mais sobre a execução do assincronismo em JavaScript, discutir as diferentes abordagens para lidar com isso e fornecer exemplos práticos para ilustrar cada técnica.

13.1 Recapitulando sobre o Assincronismo

Quando falamos sobre assincronismo em JavaScript, estamos nos referindo à capacidade do JavaScript de executar várias tarefas ao mesmo tempo, sem esperar que uma tarefa seja concluída antes de iniciar outra.

O assincronismo desempenha um papel fundamental no JavaScript, permitindo que operações demoradas, como solicitações de rede, sejam executadas em segundo plano, garantindo uma experiência de usuário responsiva e eficiente.

Essa abordagem não bloqueante é essencial para integrar-se com APIs externas e lidar com eventos em ambientes orientados a eventos, como interfaces de usuário web. Ao evitar bloqueios desnecessários, o assincronismo contribui para um fluxo contínuo de execução e uma experiência de usuário mais fluida.

13.2 Callback

Uma das maneiras mais antigas de lidar com tarefas assíncronas em JavaScript é usando callbacks. Um callback é uma função que é passada como argumento para outra função e é chamada quando uma determinada operação é concluída. No entanto, o uso excessivo de callbacks pode levar a uma situação conhecida como "callback hell", onde várias chamadas de retorno aninhadas tornam o código difícil de ler e dar manutenção.

// Função que carrega dados do usuário usando um callback
function loadUserData(callback) {
    // Simulação de uma operação assíncrona para obter dados do usuário
    setTimeout(() => {
        const userData = { id: 1, name: 'João Paulo' };
        // Chamada do callback com os dados do usuário
        callback(userData);
    }, 1000);
}

// Função de callback para processar os dados do usuário
function processData(userData) {
    console.log('Dados do usuário processados:', userData);
}

// Chamada da função para carregar os dados do usuário
loadUserData(processData);

No exemplo acima, a função loadUserData recebe um callback como argumento e simula uma operação assíncrona para obter os dados do usuário:

Após obter os dados, o código retorna um callback com os dados do usuário como argumento.

13.2.1 Callback hell

// Exemplo de Callback Hell
function loadUserData(callback) {
    getUserData((userData) => {
        getAdditionalData(userData, (additionalData) => {
            processUserData(userData, additionalData, (processedData) => {
                displayData(processedData);
            });
        });
    });
}

No exemplo acima, várias chamadas de função assíncrona estão aninhadas umas nas outras, resultando em uma estrutura conhecida como "callback hell". Isso torna o código difícil de ler e dar manutenção, especialmente à medida que mais operações são adicionadas.

13.3 Promises

As Promises foram introduzidas para lidar com o problema do callback hell. Uma Promise representa o resultado de uma operação assíncrona que pode estar pendente, resolvida ou rejeitada. Isso permite encadear chamadas de forma mais clara e legível.

// Exemplo com Promises
function loadData() {
    return getUserData()
        .then(additionalData)
        .then(processData)
        .then(displayData)
        .catch(handleError);
}

Vamos entender o passo a passo desse exemplo:

  • getUserData(): Retorna uma Promise que pode estar pendente, resolvida ou rejeitada. É responsável por obter os dados do usuário.

  • additionalData(userData): É uma função que recebe os dados do usuário como argumento e retorna uma Promise que pode estar pendente, resolvida ou rejeitada. É responsável por obter dados adicionais relacionados ao usuário.

  • processData(userData, additionalData): É uma função que recebe os dados do usuário e os dados adicionais como argumentos e retorna uma Promise que pode estar pendente, resolvida ou rejeitada. É responsável por processar os dados do usuário e adicionais.

  • displayData(processedData): É uma função que recebe os dados processados como argumento e os exibe na interface do usuário.

  • catch(handleError): Captura e trata erros que ocorrem durante o processamento dos dados.

13.4 Observables

Os Observables são uma forma mais avançada de lidar com assincronismo em JavaScript, introduzidos pelo framework RxJS. Eles são úteis para lidar com fluxos de dados assíncronos que podem produzir múltiplos valores ao longo do tempo.

O RxJS é um poderoso framework de programação reativa que implementa o padrão de Observables. Ele oferece uma ampla gama de operadores para manipular fluxos de dados de maneira eficiente e declarativa. Com o RxJS, você pode criar pipelines de dados complexos e reativos de uma forma fácil de entender e manter.

// Exemplo com Observables
const observable = new Observable(observer => {
    observer.next('Primeiro valor');
    setTimeout(() => {
        observer.next('Segundo valor');
    }, 1000);
});

observable.subscribe(value => console.log(value));

Na função acima, serão emitidos valores de string de acordo com o tempo, e cada novo valor é passado por uma função que irá imprimir os seus resultados no console do navegador.

13.5 Comparação entre Callback, Promise e Observable

Aqui está uma tabela comparativa entre Callback, Promise e Observable:

Callback Promise Observable
Manuseio de múltiplos valores Não adequado para múltiplos valores Adequado para uma única resolução Adequado para múltiplos valores ao longo do tempo
Encadeamento Tendência ao callback hell Encadeamento claro e legível Encadeamento claro e legível
Tratamento de erros Propenso a erros e dificuldade de tratamento Possui mecanismo integrado para tratamento de erros Possui mecanismo integrado para tratamento de erros
Popularidade Menos utilizado com a introdução de Promises Amplamente utilizado Crescendo em popularidade

Aqui aprendemos as várias maneiras de lidar com assincronismo em JavaScript, incluindo callbacks, Promises, Observables e o framework RxJS. Cada uma dessas técnicas tem suas próprias vantagens e situações de uso adequadas. Callbacks são úteis para tarefas simples, mas podem levar ao callback hell em casos mais complexos. Promises fornecem uma maneira mais limpa de lidar com operações assíncronas e são amplamente utilizadas na prática. Observables são ideais para lidar com fluxos de dados contínuos e podem ser especialmente úteis em aplicativos web complexos. Com o RxJS, você pode levar a programação reativa a um nível totalmente novo, manipulando fluxos de dados de maneira eficiente e declarativa.

Esperamos que este capítulo tenha sido útil para você entender melhor como trabalhar com assincronismo no JavaScript e como escolher a abordagem certa para suas necessidades de programação.