[ Conteúdo ] Funções Assíncronas

Introdução:

Boa noite pessoal! Fazia um tempo que eu não aparecia, e recentemente tive uma ideia sobre um curto artigo sobre funções assíncronas no JavaScript, que é um tópico bastante importante dado a quantidade de posibilidades que Promises nos proporciona na linguagem e seus derivados como fetch e assim por diante.

Bom, não sei se tem algum artigo sobre aqui (perdão por não procurar antecipadamente). Se existir, encare isso como minha versão sobre o assunto. Dito isso, vamos para o que interessa.

Considerações:

Naturalmente, você deve dominar toda a base do JavaScript, em especial entender como Promises e functions se comportam na linguagem.

Além disso, farei breve menções sobre tipagem, ou seja, envolve um pouco de TypeScript, mas nada muito complexo.

O que são funções assíncronas?

O nome por si só já é bem sugestivo: São funções que lidam com operações assíncronas. Funções assíncronas nos permite escrever de maneira elegante e clara operações assíncronas ou lidar com operações assíncronas externas (retornada por outras funções).

Não é difícil criar uma função assíncrona, basta utilizar a palavra chave async antes da palavra chave function. Confuso? Então um breve exemplo:

async function MinhaFuncaoAssincrona() {
    //...
}

Agora, tudo que eu faço dentro desde escopo se comporta como uma Promise? A resposta é, não exatamente...

Assim como tudo na área de tecnologia, nada é tão simples assim, mas não é tão difícil, apenas existe o jeito certo de fazer as coisas. Funções assíncronas não param por aqui, tem mais features que ajudama a ter um comportamento melhor.

Mas, se você quer apenas um retorna de uma Promise, então sim, o código acima retorna uma Promise e mais abaixo eu explico em detalhes.

Palavra chave await:

Quando declaramos uma função assíncrona, nos é permitido a utilização da palavra chave await dentro de seu escopo.

E para que serve o await, bom, aqui é que entra a mágica! A palavra chave await basicamente diz que as operações abaixo devem esperar a operação que tem um await para pode continuar. Confuso? O comportamento é igual ao padrão do JavaScript, ou seja, parece síncrono.

Então vamos tentar ilustrar melhor com um exemplo usando 2 fetch:


const myFetch1 = fetch('url1');
const myFetch2 = fetch('url2');

async function MinhaFuncaoAssincrona() {
    const first = await myFetch1.then(result => result.json());
    const second = await myFetch2.then(result => result.json());
}

Acima, temos 2 fetch que retornam uma Promise, ou seja, uma operação de natureza assíncrona. Dento da função assíncrona, temos duas constante que convertem os resultados do fetch para json, mas aqui, a ordem é deterministica.

A constante second vai esperar a constante first para poder converter para json. Simples, não? A operação ainda é assíncrona. Seu programa ou site não vai parar até esse primeiro fetch ser convertido, apenas dentro deste escopo, dentro desta função assíncrona, controlamos a ordem das coisas.

Entender sobre concorrência ajuda a entender com mais precisão sobre a ordem.

a ordem que os primeiro fetch são resolvidos é não deterministica. O que isso quer dizer? Quer dizer que não temos como saber qual vai ser resolvido primeiro. Entranto, o await controla a ordem em que eles são convertidos.

Sem await, não é assíncrono:

Na documentação deixa bem claro que uma função assíncrona, que não tenha nenhum await nela é executada de forma síncrona:

async function HandleClick() {
    // está função não é executada de forma assíncrona. 
}

Além disso, você pode ter qualquer quantidade de await em funções assíncrona, até zero, que cai no caso acima.

Funções assíncronas sempre retornam promises:

Sim, isto aqui é bem interessante. Independemente se o valor retornando não for uma Promise, o valor será embrulhado em uma Promise:

async function HandleMyAge(age) {
    return age;
}

HandleMyAge(22) // Promise<number>

Isto aqui é mais evidente no TypeScript, por isso que nunca usou pode não entender muito bem o que é Promise<number>. Apenas saiba que é uma Promise do tipo number, de forma resumida :smile:

Voltando ao assunto: Funções assíncronas sempre retornam uma Promise, mesmo quando não tem um return explicitamente declarada. Funções que não tem return, não retornam nada, ou seja, tem o tipo de retorno definido para void. Entretando, em funções assíncronas, até esse void é embrulhado em uma promise:

async function HandleVoid() {
    // retorna Promise<void> que resolve para `undefined`
}

Isso pode abrir algumas possibilidades, pois mesmo resolvendo para undefined, você pode encadear com o then, dando que no fim, é uma Promise.

async function HandleVoid() {
    // retorna Promise<void> que resolve para `undefined`
}

HandleVoid()
.then(() => console.log("fazer algo"))
.then(() => console.log("fazer outra coisa"))

Conclusão:

EU diria que é um bom conteúdo introdutório sobre funções assíncronas, não? Na verdade, basicamente se resume a isto ai, não tem nada mais técnico sobre, além de técnicas em que funções assíncronas estão envolvidas ou nuances que diferem de caso para caso.

Então é isso, espero que tenham gostado.

Fiquei meio perdido, com await não era para ser síncrona?

Não. O `core` de funções assíncronas é entender como funciona o `await`. Considere o seguinte exemplo: Um chefe de cozinha tem que preparar um frango ao molho dentro de 1 horas. Se ele fizer tudo em sequência, ou seja, primeiro o frango, depois o molho, logo em seguida em pratar e finalizar com acompanhamentos, demoraria demais. (isto seria uma operação síncrona, tudo feito uma por vez, não ao mesmo tempo). Agora, qual é a solução para isso? Simples, temos que fazer tudo ao mesmo tempo intercalando (operação assícnrona concorrente). Então o chefe coloca o frango no forno, enquanto isso prepara o molho e coloca no fogo. Enquanto o frango e o molho esta no fogo, ele precisa separar os pratos e deixar em posição para colocar o frango. O molho terminou primeiro que o frango, mas não podemos colocar o molho no prato e depois o frango, tem que ser o frango e depois o molho, e agora? Agora ele "ESPERA" (`await`). Então, quando o frango estiver pronto, ele coloca no prato e logo em seguida, o molho que estava esperando é colocado por cima. Consegue ver a analogia? Quando queremos que uma operação só seja concluída somente quando **outra** termine, queremos esperar, e é exatamente isso que o `await` faz, deixa tudo abaixo dele esperando até que atividadade em processo termine, para poder começar outra. E tudo aquilo que leva tempo para ser concluído, ou seja, não bloqueia o resto do programa abaixo dele, no caso, a função assíncrona não vai bloquear o resto do programa que é síncrono, é uma operação assíncrona. Considere o seguinte código: ```js async function HandleMyFetch() { const myFetch = await fetch('url') // mesmo que demore 10 segundos, o resto do programa fora do escopo desta função // continua sendo interpretado. return myFetch; // retorna uma Promise com o valor do retornado do Fetch. Se não fosse o "await", iria // retorna "undefined" pois o retorno iria executar antes do "fetch" estar pronto, e // não esperar. } ``` Não sei se deu para entender. Mas não tenha vergonha em perguntar. Eu responderei todas as dúvidas que você tiver :smile:
Opa, eu entendi sim, quando fiz a pergunta eu já fui pesquisar sobre, descobri que estava usando async await de forma errada em quase 100% dos casos kkkkkkkkkkk.
Não há vergonha nisso, Meu nobre. O importante é aprender. Eu também usei por bastante tempo async/await errado por muito tempo. Não só isso na verdade. Escrever este artigo foi como um certificado para mim mesmo, de que eu finalmente entendi. Espero ter ajudado de alguma forma :)

cara, primeiro muito obrigado pelo post. Me veio uma dúvida aqui. você disse que uma função assíncrona sem await é uma função síncrona:

`async function HandleMyAge(age) { return age; }

HandleMyAge(22) // Promise`

O exemplo acima, do seu texto, retorna mesmo uma promise, ou no caso faltou declarar dessa forma:

`async function HandleMyAge(age) { await _age = age return _age }

HandleMyAge(22) // Promise`

Valeu mesmo por compartilhar seu conhecimento por aqui!

Eu não sei se entendi bem a sua dúvida, mas irei explicar pelo o que eu acredito que seja. Você mencionou sobre o fato de funções sem `await` serem executadas de forma sincrona, e mostrou um exemplo meu que não tem `await`, mas retorna uma promise. Explicação: uma função assíncrona sempre vai embrulhar o retorno em uma `promise`. É importante enfatizar o "embrulhar". Embrulhar em uma promise, não é uma operação assíncrona, e sim síncrona. Então com ou sem `await` vai retornar uma promize pois o valor foi "embrulhado". Embrulhar é apenas pegar o valor de retorna e transformar aquele valor numa Promise. A questão das funções sem `await` se comportar como funções regulares (síncrona) é apenas na execução. Então sem `await`, é como uma função regular, mas que embrulha numa Promise. O `await` é para garantir que todo o resto do escopo abaixo de onde esta o 'await', deva esperar. Isso é controlado pelo o Event Loop, que gerencia operações assíncronas concorrentemente. Não sei se era sua dúvida, mas estou sempre a disposição no que precisar.
Cara, brigadão pela resposta! entendi o ponto. Mesmo não tendo o await, apesar da função se comportar como uma função síncrona, ainda assim será retornado uma promise. Todo sucesso do mundo e mais um pouco pra você!

async/await e then/catch tem diferença importantes embora ambas lidem com promise. entender como cada uma funciona é muito importante.

um colega estava usando express e a função que lidava com requisição HTTP estava usando then/cath, e dentro deles estava retornando a response com os dados json. quem sabe do que estou falando sabe que a função handler vai retornar undefined igorando o return codado.

Sim, é verdade. Por isso mencionei que deve compreender como Promises se comportam. O then e o catch não tem nada aver com funções assíncronas, e sim com suas operações que são feitas usando Promises. Gostei desde comentário. Quem estiver lendo e ver, já vai sacar se tiver uma dúvida.