Desvendando o Mecanismo do async/await em JavaScript

JavaScript é uma linguagem de execução em uma única thread, o que pode levar a bloqueios síncronos indesejados. Para mitigar isso, foram desenvolvidos mecanismos assíncronos não bloqueantes, como callbacks, objetos Promise no ES6 e funções Generator. Cada abordagem trouxe avanços, mas ainda exigiam conhecimento de abstrações complexas. Com a introdução do async/await no ES7, a manipulação de operações assíncronas se tornou mais intuitiva, permitindo escrever código que parece síncrono, sem aninhamentos de callbacks ou múltiplos then.

Uso Prático do async/await

Ao utilizar async/await, evitamos a complexidade de encadeamentos. É recomendável envolver as chamadas await em blocos try...catch para capturar possíveis rejeições das Promises.


function obterDadosSimulados() {
    return new Promise((resolve, reject) => {
        const tempoExecucao = Math.floor(Math.random() * 2000) + 500;
        const sucesso = Math.random() > 0.1;
        setTimeout(() => {
            if (sucesso) resolve({ tempo: tempoExecucao });
            else reject(new Error('Falha na simulação'));
        }, tempoExecucao);
    });
}

async function processarEmSequencia() {
    try {
        const primeiro = await obterDadosSimulados();
        console.log('Etapa 1:', primeiro);
        const segundo = await obterDadosSimulados();
        console.log('Etapa 2:', segundo);
        const terceiro = await obterDadosSimulados();
        console.log('Etapa 3:', terceiro);
    } catch (erro) {
        console.error('Erro durante processamento:', erro.message);
    }
}

processarEmSequencia();

O async/await funciona como açúcar sintático para funções Generator. Assim como as Promises simplificam callbacks, o async/await combina Generator e Promise de forma mais legível. Podemos replicar esse comportamento usando Generator com funções Thunk, onde async é substituído por * e await por yield.


function tarefaAssincronaThunk() {
    return function(callback) {
        const atraso = Math.random() * 1500;
        setTimeout(() => callback(atraso), atraso);
    };
}

function* fluxoDeTrabalho() {
    const etapa1 = yield tarefaAssincronaThunk();
    console.log('Resultado 1:', etapa1);
    const etapa2 = yield tarefaAssincronaThunk();
    console.log('Resultado 2:', etapa2);
    const etapa3 = yield tarefaAssincronaThunk();
    console.log('Resultado 3:', etapa3);
}

function motorDeExecucao(gerador) {
    const iterador = gerador();

    function avancar(dado) {
        const passo = iterador.next(dado);
        if (passo.done) return;
        passo.value(avancar);
    }

    avancar();
}

motorDeExecucao(fluxoDeTrabalho);

Gerenciamento Automático de Fluxo

Funções async incluem um executer interno. Para simular isso menualmente, podemos usar Generator com Thunks ou Promises.

Implementação com Thunks

A função motorDeExecucao controla o avanço do Generator. Quando next() é chamado com um parâmetro, ele é atribuído à variável à esquerda do último yield. A função Thunk executa a operação assíncrona e invoca o próximo passo no callback.


function simularOperacao() {
    return function(prosseguir) {
        const valor = Math.random() * 100;
        const tempo = Math.random() * 1000;
        setTimeout(() => prosseguir(valor), tempo);
    };
}

function* cadeiaDeProcessos() {
    const x = yield simularOperacao();
    console.log('Processo A concluído com:', x);
    const y = yield simularOperacao();
    console.log('Processo B concluído com:', y);
    const z = yield simularOperacao();
    console.log('Processo C concluído com:', z);
}

function executarCadeia(gerador) {
    const g = gerador();

    const proximo = (dado) => {
        const resultado = g.next(dado);
        if (resultado.done) return;
        resultado.value(proximo);
    };

    proximo();
}

executarCadeia(cadeiaDeProcessos);

Implementação com Promises

Promises simplificam o fluxo, pois encapsulam o estado da operação assíncrona e permitem o encadeamento com then.


function tarefaComPromessa() {
    return new Promise((resolver) => {
        const atrasoAleatorio = Math.random() * 800 + 200;
        const dados = { timestamp: Date.now() };
        setTimeout(() => resolver(dados), atrasoAleatorio);
    });
}

function* sequenciaAssincrona() {
    const res1 = yield tarefaComPromessa();
    console.log('Resposta 1:', res1);
    const res2 = yield tarefaComPromessa();
    console.log('Resposta 2:', res2);
    const res3 = yield tarefaComPromessa();
    console.log('Resposta 3:', res3);
}

function gerenciarFluxo(gerador) {
    const g = gerador();

    const proximoPasso = (dado) => {
        const iteracao = g.next(dado);
        if (iteracao.done) return;
        iteracao.value.then(d => proximoPasso(d));
    };

    proximoPasso();
}

gerenciarFluxo(sequenciaAssincrona);

Uma versão mais robusta do gerenciamento de fluxo pode incluir tratamento de erros e suporte a Promises rejeitadas.


function operacaoComRisco() {
    return new Promise((resolve, reject) => {
        const chance = Math.random();
        const tempo = Math.random() * 1000;
        setTimeout(() => {
            if (chance > 0.3) resolve({ id: Math.floor(chance * 100) });
            else reject(new Error('Falha na operação'));
        }, tempo);
    });
}

function* fluxoComTratamento() {
    try {
        const etapa1 = await operacaoComRisco();
        console.log('Etapa 1:', etapa1);
        const etapa2 = await operacaoComRisco();
        console.log('Etapa 2:', etapa2);
    } catch (e) {
        console.error('Erro capturado:', e.message);
    }
}

function executorCompleto(gerador) {
    return new Promise((resolve, reject) => {
        const g = gerador();

        function avancar(dado) {
            let iteracao;
            try {
                iteracao = g.next(dado);
            } catch (erro) {
                return reject(erro);
            }
            if (!iteracao) return reject(new Error('Iteração nula'));
            if (iteracao.done) return resolve(iteracao.value);
            Promise.resolve(iteracao.value).then(
                proximo => avancar(proximo),
                erro => reject(erro)
            );
        }

        avancar();
    });
}

executorCompleto(fluxoComTratamento).then(() => {
    console.log('Fluxo finalizado com sucesso');
}).catch(err => {
    console.error('Erro no executor:', err.message);
});

Tags: javascript async-await generators promises es7

Publicado em 6-6 03:25 por Thomas