nomar.dev

11011000001

28 de Dezembro de 2020

Programação assíncrona e JavaScript

Real-time sometimes, asynchronous most of the time.

It Doesn't Have to Be Crazy at Work

Muitas funções que chamamos quando estamos programando nos devolvem um resultado de forma imediata. Isso torna a nossa vida mais fácil pois temos um controle melhor do estado de uma aplicação. Sabemos o que pode causar uma mudança no estado da aplicação e em que ordem isso ocorre.

Entretanto algumas funções podem levar um longo tempo até que um resultado esteja disponível. Normalmente essas funções precisam interagir com recursos externos ao processador. Por exemplo, a requisição de um dado usando uma rede de computadores ou do disco rígido. Nesse ponto, a velocidade de execução de uma função não depende apenas do processador.

Suponha que precisamos escrever um programa que recupere dois valores que estão armazenados em dois computadores diferentes por meio de uma requisição de rede usando o protocolo HTTP. Depois de obter esses valores, realizar a soma e apresentar esse valor para o usuário.

Os passos desse programa poderiam ser:

  1. fazer a requisição do valor para um servidor
  2. armazenar o valor obtido na variável valor_1
  3. fazer a requisição do valor para o outro servidor
  4. armazenar o valor obtido na variável valor_2
  5. fazer a soma da variável valor_1 e do valor_2
  6. exibir o resultado da soma para o usuário

Os passos 1 e 3 são os mais críticos porque é o momento onde os valores que estão em outros computadores serão recuperados. Com o intuito de facilitar nosso exemplo, vamos considerar que esses passos podem levar de um a três segundos para retornarem o valor. Sabendo disso, podemos dizer que no pior caso nosso programa demoraria seis segundos para exibir o resultado para o usuário. Veja que os demais passos (por serem executados de forma "imediata") não alteram o tempo de execução do programa.

Chegamos nesse tempo máximo porque foi considerado que no passo 1 e no 3, o programa vai esperar o valor ser retornado e só depois irá para o próximo passo. Ou seja, a execução do programa é pausada enquanto o retorno ainda não está disponível. É dessa forma que a programação síncrona ocorre.

Acredito que seja a forma mais natural de imaginar a execução de um programa: dando um passo de cada vez e esperando a execução completa de um passo até passar para o próximo. A programação síncrona com certeza nos leva para um lugar seguro quando estamos programando e não existe nada de errado em ficar nesse lugar.

Entretanto esperar até que o resultado de um passo esteja disponível para prosseguir com a execução do programa pode não ser uma opção viável em alguns casos. Imagine que você esteja navegando em um site e uma função JavaScript demorasse dez segundos. Durante esses dez segundos a execução do JavaScript ficaria pausada e mesmo que você tentasse selecionar um texto da página, só depois desses dez segundos que você veria o texto selecionado. Isso não é nem um pouco bom especialmente no navegador onde é comum fazer mais de uma coisa ao mesmo tempo. Por essa razão (e provavelmente por outras) que o JavaScript trabalha de forma assíncrona.

A programação assíncrona permite que a execução do programa continue mesmo que o resultado de uma função ainda não esteja disponível. Os passos que descrevemos para o nosso programa seriam executados de forma sequencial, mas não a transição de um passo para o outro não depende do resultado da função estar disponível.

É importante destacar a palavra "sequencial" da frase anterior. Pois um erro de entendimento que já presenciei algumas vezes é achar que todos os passos são disparados de uma vez ou que a ordem dos passos não é garantida na execução do programa pelo fato de ser assíncrono.

Se executarmos o nosso programa mil vezes, o passo um sempre vai ser chamado antes do dois, o passo dois antes do três e assim por diante. O que pode mudar em cada execução é quando o resultado de uma função assíncrona estará disponível.

Continuar a execução do programa mesmo que o resultado não esteja disponível é uma vantagem que nos salta aos olhos. Entretanto uma vantagem normalmente está associada a um custo e aqui não seria diferente. Agora é preciso lidar e coordenar os resultados das ações assíncronas e como elas podem alterar o estado da aplicação.

No JavaScript, temos as funções de callback e Promises para lidar com ações assíncronas. Uma função de callback é passada como parâmetro para uma função assíncrona para que seja executada quando o resultado estiver disponível no futuro. Já uma Promise é um objeto que representa um evento futuro que pode ocorrer ou falhar e que permite adicionar callbacks para os cenários de sucesso ou falha. Diferente da primeira abordagem, os callbacks são adicionados a Promise e não passados como parâmetro da função assíncrona.

Ao programar com JavaScript, em muitos casos vamos ter que usar uma função assíncrona. Uma vez que uma função assíncrona é usada, o código se torna assíncrono e é preciso lidar com isso. Assim, entender o funcionamento básico e os impactos da programação assíncrona nos ajudar na hora de criar e especialmente na hora de resolver um problema pois teremos uma ideia melhor de onde e o que procurar.