Explorando funções assíncronas no Javascript

Explorando funções assíncronas no Javascript

Hoje vamos explorar a sincronicidade no Javascript? Você sabe como utilizar uma função assíncrona?

Programação Assíncrona

Se você já realizou alguma ação em seu código, e teve que esperar uma resposta —por exemplo, um fetch a uma API, você já trabalhou com programação assíncrona.

Sincronicidade no Javascript, é quando precisamos trabalhar com uma função que acontece em segundo plano, enquanto o restante do nosso código continua a rodar normalmente.

Na prática, isso pode parecer um pouco estranho, afinal estamos acostumados com uma linha de código rodando atrás da outra.

Veja o exemplo:

const mensagemAposSegundos = (mensagem, segundos) => {
  setTimeout(() => {
    console.log(mensagem);
  }, segundos * 1000);
}

const iniciaPrograma = () => {
  mensagemAposSegundos('mensagem 1', 2);
  mensagemAposSegundos('mensagem 2', 1);
}

iniciaPrograma();

No código acima, criei a função mensagemAposSegundos que escreve no console uma mensagem qualquer após x segundos. Veja utilizei essa função 2 vezes, querendo escrever a “mensagem 1” após 2 segundos e a “mensagem 2” após 1 segundos.

Lendo este código naturalmente, sem pensar em sincronicidade, o esperado seria o meu programa começar a rodar e após 2 segundos a “mensagem 1” ser exibida. Depois de mais 1 segundo, teríamos a “mensagem 2” no console. Mas, na prática, a resposta é:

mensagem 2
mensagem 1

Pode parecer estranho, mas com Javascript, o que acontece é: a primeira função para exibir a “mensagem 1” é lida, e começa a rodar em segundo plano. Enquanto isso, o código já passa para a linha seguinte, onde é chamada a função para exibir a “mensagem 2” e também começa a rodar em segundo plano. Como a “mensagem 2” precisa de apenas 1 segundo para ser exibida, ela acontece primeiro, e só depois de 2 segundos a “mensagem 1” é exibida.

Em alguns casos, queremos que o código exibido na ordem correta, e precisamos pedir para “esperar” a função terminar para ir a próxima linha. Com Javascript, precisamos transformar essa em função comum em uma função assíncrona, utilizando os recursos de async, await e promises.

Async, await e Promises

Uma função assíncrona em Javascript é uma Promise, um método que “promete” de entregar um resultado, seja ele positivo com um resolve ou negativo com um reject. Ao chamar uma função que retorna uma Promise, transformo essa função em assíncrona, e posso pedir ao código que “espere” essa função terminar, utilizando o prefixo await ou o método then para resultados positivos e catch para resultados negativos.

Em nosso exemplo anterior, para funcionar como esperado, precisamos criar uma Promise na função mensagemAposSegundos, prometendo sinalizar a conclusão dessa função somente após a exibição da mensagem. Assim, ao chamar a função posso pedir explicitamente ao código para esperar ser concluída com await ou realize uma ação após a sua conclusão com then

const mensagemAposSegundos = async (mensagem, segundos) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(mensagem);
      resolve();
    }, segundos * 1000);
  });
}

const iniciaPrograma = async () => {
  await mensagemAposSegundos('mensagem 1', 2);
  await mensagemAposSegundos('mensagem 2', 1);

    //ou

  mensagemAposSegundos('mensagem 1', 2).then(() => {
    mensagemAposSegundos('mensagem 2', 1);
  });
}

iniciaPrograma();

Desta forma, estamos indicando que a nossa função, que promete entregar um resultado com a Promise só é concluída, a partir do resolve(), após a exibição da mensagem. Como ela é uma função assíncrona, marcada com async, podemos pedir para esperar a função ser concluída com await, ou criar uma árvore de resultados com then, chamando a segunda mensagem somente após a primeira ter sido concluída. Tendo como resultado:

mensagem 1
mensagem 2

Na prática

Funções assíncronas e Promises já são bastante comuns durante o desenvolvimento, seja em uma API quando você cria um registro no banco de dados, ou seja, no frontend quando há uma requisição para uma API. Essas funções devem ser usadas com bastante cuidado para não “dessincronizar o seu código”.

No exemplo abaixo, você tem uma função fetch, para pegar os dados de um usuário a partir de um endpoint de uma API e conferir se este usuário está logado.

const isUserLogged = (userId) => {
  const response = fetch(`https://example.com/api/user/${userId}`);
  const user = response.json();

  return user.isLogged;
}

Neste formato, sua função estaria errada. O fetch e a resposta do fetch são Promises, ou seja, são funções assíncronas, que acontecem apenas em segundo plano. Sem um pedido explicito de pedido de espera de finalização dessa função, a variável user estará vazia, ainda sem resposta, no retorno user.isLogged. O correto é algo como:

const isUserLogged = async (userId) => {
  const response = await fetch(`https://example.com/api/user/${userId}`);
  const user = await response.json();

  return user.isLogged;
}

Dessa forma, estamos solicitando que o código espere a função fetch ser finalizada, e espere a resposta ser transformada em JSON com response.json(). Assim, ao ter a variável user lida em user.isLogged todos, os dados estarão presentes.

Este é um assunto bastante importante para desenvolvedores iniciantes, e veja que um pequeno deslize no código pode deixar tudo bastante confuso; por isso, é importante sempre ter bastante atenção no uso de Promises, Asyncs e Awaits, mas principalmente, entender o que realmente está acontecendo por trás dos códigos. Estudar estes conceitos da linguagem com certeza irão resultar em melhores códigos em seus projetos.

Para ficar por dentro dos nossos próximos conteúdos, participe do nosso canal no Discord e se inscreva em nossa newsletter!