Assincronicidade, Promessas e Callbacks

Assincronicidade é aquele tipo de palavra que parece que aponta pra algo complicado - mas não é.

É bem simples, na real.

A ideia é a seguinte - você não espera algo acontecer para continuar fazendo outras coisas.

Saca só:

Digamos que você queira sair com alguém final de semana.

Você manda uma mensagem no Whatsapp para essa pessoa perguntando se ela quer sair.

E aí? Você deixa a conversa aberta - olhando para o celular - aguardando resposta? Não!

Bom - espero que não.

Você vai fazer outras coisas.

Quando a pessoa responder, seu celular vibra e aí você sabe que recebeu uma resposta - seja positiva ou negativa.

Ponto é - Você não fica esperando a resposta chegar para fazer outras coisas. Quando chegar, chegou.

Isso é assincronicidade.

Outro exemplo - você vai em um restaurante e faz um pedido para o garçom - uma coca zero e um sanduíche. Coisa leve.

O garçom leva o pedido para a cozinha e vida que segue.

Agora, imagine se o garçom ficasse olhando fixamente pro cozinheiro preparar o pedido:

"Tá pronto? O sanduíche do cara lá, meu 🥪. Tá pronto? Ajuda aí pô 🤌🤌, prepara o negócio. Já levei a coquinha lá."

Não iria funcionar muito bem.

Então, ele vai nas outras mesas anotar outros pedidos.

Quando o seu sanduíche estiver pronto - o cozinheiro avisa ele.

Assincronicidade. Essa é a ideia.

Agora - vamos aplicar isso aí para um software.

Pega o Whatsapp - por exemplo.

Você precisa ficar recarregando a conversa toda vez que recebe uma mensagem? Não.

As mensagens automaticamente aparecem.

Isso é assincronicidade. Em algum lugar da arquitetura do Whatsapp assincronicidade foi usada.

É simples assim. Papo reto.

Agora - existem algumas maneiras de fazermos isso - técnicamente falando.

Vou cobrir três - mais específicamente sobre programação assíncrona:

  1. Callbacks
  2. Promises
  3. Async/Await

Só uma nota:

Tente puxar a essência por trás dessas ideias.

Kotlin, Java, .NET - todas que suportam assincronicidade tem algo parecido. Serião.

Outra coisa - programação assíncrona é diferente de arquitetura assíncrona.

No caso do Whatsapp - no exemplo - arquitetura assíncrona foi utilizada - mas o conceito de assincronicidade continua o mesmo.

A diferença é que a arquitetura é em nível mais abrangente.

Enfim - callbacks.

1- Callbacks

Callbacks são talvez a maneira mais antiga de implementar programação assíncrona.

Imagina um negócio comigo aqui - você tem uma loja - e também tem um sistema que te avisa quando uma encomenda chega na casa dos clientes.

Inovador, né? Pois é.

Agora, imagina que em algum lugar dessa aplicação tem essa função aqui:

function avisarQueEncomendaChegou(encomenda) {
alert('Usuário foi informado que a ' + encomenda + ' chegou 📦.')
}

Simples, certo? Ela informa que um usuário foi notificado que uma encomenda chegou.

E agora - como faremos para identificar que uma encomenda chegou? Essa função não ajuda muito...

Imaginemos novamente - toda vez que uma encomenda chega, o entregador aperta um botão no celular dele avisando que a encomenda chegou - e esse botão roda essa função aqui:

function concluirEntrega(encomenda) {
alert('A entrega da encomenda ' + encomenda + ' está sendo processada...');
fetch('https://servico-de-encomendas.com/?encomenda=' + encomenda);
}

A gente acessa o serviço 'https://servico-de-encomendas.com' avisando que chegou uma encomenda.

Só - e somente só - quando esse serviço retornar um ok, só aí informamos o usuário que o pacote dele chegou.

E agora - como juntamos tudo?

Com um callback:

function avisarQueEncomendaChegou(encomenda) {
alert('Usuário foi informado que a ' + encomenda + ' chegou 📦.')
}
function concluirEntrega(encomenda, callback) {
alert('A entrega da encomenda ' + encomenda + ' está sendo processada...');
fetch('https://servico-de-encomendas.com/?encomenda=' + encomenda)
.then(() => {
callback(encomenda);
});
}
const encomenda = "15 latas de atum e uma bermuda da Mormaii";
concluirEntrega(encomenda, avisarQueEncomendaChegou);

Em outras palavras - um callback é uma função que é passada como argumento para outra função - que a executa quando termina algo.

Parece complexo - mas é basicamente como se a função ali disesse:

"Ó - de boa - eu concluo a entrega pra você. Só me informe qual foi a encomenda e como avisar pro destinatário que ela chegou"

Para cenários simples isso parece ok. Mas como tudo - tem seus problemas.

Imagine que antes de avisar o usuário, você tem que processar o pagamento e atualizar o estoque:

function confirmarPagamento(encomenda, callback) {
alert('Confirmando pagamento para a encomenda: ' + encomenda + '...');
fetch('https://servico-de-pagamento.com/?encomenda=' + encomenda)
.then(() => {
callback(encomenda);
});
}
function atualizarEstoque(encomenda, callback) {
alert('Atualizando estoque para a encomenda: ' + encomenda + '...');
fetch('https://servico-de-estoque.com/?encomenda=' + encomenda)
.then(() => {
callback(encomenda);
});
}
const encomenda = "15 latas de atum e uma bermuda da Mormaii";
concluirEntrega(encomenda, (encomenda) => {
confirmarPagamento(encomenda, (encomenda) => {
atualizarEstoque(encomenda, (encomenda) => {
avisarQueEncomendaChegou(encomenda);
});
});
});

Começa a ver o caos?

Agora imagina se você tivesse outras 10 coisas a fazer antes de avisar o usuário.

Problema, né? Pois é.

Isso é popularmente conhecido como callback hell 🔥👹🔥.

O inferno dos callbacks acontece quando você tem muitos callbacks - geralmente um dependendo do outro.

Em cenários muito complexos - callbacks geralmente começam a te dar dor de cabeça - especificamente por isso.

Mas há alternativas.

2- Promises

Ainda lembro da primeira vez que ouvi falar em promises - foi na época da faculdade.

Acho que estávamos desenvolvendo um sistema em .NET com Angular, não me lembro direito... Mas era pra controle de carrinho - tipo de e-commerce - exemplo clássico.

O professor disse algo nas linhas de: "[...] A questão das promises é a seguinte - você tem uma requisição. E aí você promete que ela será retornada. Quando? Só Deus sabe".

E é exatamente isso. Só Deus sabe. É uma promessa - e somente isso.

Vai resolver? Talvez.

Agora? Provavelmente não - mas em algum momento do futuro.

Quando? Só Deus sabe.

Devemos confiar? É...

Assim como todas as promessas da vida.

Então não vale travar nosso fluxo da aplicação aguardando a requisição voltar - afinal - é uma promessa.

E somente isso.

Na real - já usamos Promises no exemplo lá em cima - eu só não mencionei.

O .then que você viu lá depende de uma Promise. O retorno do acesso ao serviço do fetch é uma promessa.

Eu fiz isso mais para simplificar o exemplo dos callbacks, mas a ideia continua a mesma.

De qualquer maneira - se a gente mudasse nosso exemplo da encomenda para usar apenas Promises, teríamos algo do tipo:

function concluirEntrega(encomenda) {
alert('A entrega da encomenda ' + encomenda + ' está sendo processada...');
return fetch('https://servico-de-encomendas.com/?encomenda=' + encomenda);
}
function confirmarPagamento(encomenda) {
alert('Confirmando pagamento para a encomenda: ' + encomenda + '...');
return fetch('https://servico-de-pagamento.com/?encomenda=' + encomenda);
}
function atualizarEstoque(encomenda) {
alert('Atualizando estoque para a encomenda: ' + encomenda + '...');
return fetch('https://servico-de-estoque.com/?encomenda=' + encomenda);
}
const encomenda = "15 latas de atum e uma bermuda da Mormaii";
concluirEntrega(encomenda)
.then(() => confirmarPagamento(encomenda))
.then(() => atualizarEstoque(encomenda))
.then(() => alert('Usuário foi informado que a ' + encomenda + ' chegou 📦.'))
.catch(error => alert('Erro no processo: ' + error));

Veja que não estamos mais usando callbacks.

Fato é - quando rodamos a função concluirEntrega, basicamente falamos:

"Ó, eu te prometo que o https://servico-de-encomendas.com vai retornar algo. Só não sei quando."

Pode ser nunca? Pode.

Pode ser daqui 1 minuto? Pode.

Pode ser agora? Pode.

Independente disso, não vale travar o fluxo da nossa aplicação aguardando essa promessa ser cumprida.

Precisamos tocar o barco.

... Tá, mas e se precisarmos fazer aquele mesmo esquema do callback hell?

Então - vai ficar mais limpo - saca só:

function avisarQueEncomendaChegou(encomenda) {
alert(`Usuário foi informado que a ${encomenda} chegou 📦.`);
}
function concluirEntrega(encomenda) {
alert(`A entrega da encomenda ${encomenda} está sendo processada...`);
return fetch(`https://servico-de-encomendas.com/?encomenda=${encomenda}`);
}
function confirmarPagamento(encomenda) {
alert(`Confirmando pagamento para a encomenda: ${encomenda}...`);
return fetch(`https://servico-de-pagamento.com/?encomenda=${encomenda}`);
}
function atualizarEstoque(encomenda) {
alert(`Atualizando estoque para a encomenda: ${encomenda}...`);
return fetch(`https://servico-de-estoque.com/?encomenda=${encomenda}`);
}
const encomenda = "15 latas de atum e uma bermuda da Mormaii";
concluirEntrega(encomenda)
.then(() => confirmarPagamento(encomenda))
.then(() => atualizarEstoque(encomenda))
.then(() => avisarQueEncomendaChegou(encomenda))
.catch(error => alert(`Erro no processo: ${error}`));

Ainda assim - tá meio confuso, não? Dependendo do tanto de dependência que precisamos rodar antes - pode ficar caótico.

... E aí temos o famoso async/await.

3- Async/Await

O Async/await é o querido de muita gente.

Ele é montado em cima das Promises - só que ele te dá uma sintaxe mais legal para trabalhar.

No mesmo exemplo - teríamos algo do tipo:

async function processarEntrega(encomenda) {
try {
await concluirEntrega(encomenda);
await confirmarPagamento(encomenda);
await atualizarEstoque(encomenda);
alert('Usuário foi informado que a ' + encomenda + ' chegou 📦.');
} catch (error) {
alert('Erro no processo: ' + error);
}
}
const encomenda = "15 latas de atum e uma bermuda da Mormaii";
processarEntrega(encomenda);

Em resumo - async indica que sua função vai retornar uma Promise.

E await aguarda o retorno da Promise.

Não tem segredo.

A diferença é só a aproximação.

Dependendo do cenário - as vezes é legal usar Callbacks - tipo em situações mais simples.

Agora, quando começa a ficar mais complicado - ja é legal refatorar para usar Promises.

... E é isso.

-Rapozo