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);
});