O que é e como usar

Ao escrever aplicativos JavaScript, você pode ter encontrado funções assíncronas, como a função de busca no navegador ou a função readFile no Nodejs.

Você pode ter obtido resultados inesperados se tiver usado qualquer uma dessas funções como faria normalmente. Isso ocorre porque são funções assíncronas. Este artigo orienta o que isso significa e como usar funções assíncronas como um profissional.

Introdução à função síncrona

JavaScript é uma linguagem de thread único que só pode fazer uma coisa de cada vez. Isso significa que, se o processador encontrar uma função que demora muito, o JavaScript espera até que toda a função seja executada antes de passar para outras partes do programa.

A maioria das funções é executada inteiramente pelo processador. Isso significa que durante a execução das referidas funções, independentemente do tempo que demore, o processador estará completamente ocupado. Estas são chamadas de funções síncronas. Um exemplo de função síncrona foi definido abaixo:

function add(a, b) {
    for (let i = 0; i < 1000000; i ++) {
        // Do nothing
    }
    return a + b;
}

// Calling the function will take a long time
sum = add(10, 5);

// However, the processor cannot move to the 
console.log(sum);

Esta função executa um grande loop cuja execução leva muito tempo antes de retornar a soma de seus dois argumentos.

Após definir a função, nós a chamamos e armazenamos seu resultado na variável sum. Em seguida, registramos o valor da variável sum. Embora a execução da função add demore um pouco, o processador não pode prosseguir para registrar a soma até que a execução seja concluída.

A grande maioria das funções que você encontrará se comportará de maneira previsível, como a descrita acima. No entanto, algumas funções são assíncronas e não se comportam como funções regulares.

Introdução à função assíncrona

Funções assíncronas fazem a maior parte de seu trabalho fora do processador. Isso significa que mesmo que a função demore um pouco para ser executada, o processador estará desocupado e livre para fazer mais trabalho.

Aqui está um exemplo de tal função:

fetch('https://jsonplaceholder.typicode.com/users/1');

Para aumentar a eficiência, o JavaScript permite que o processador passe para outras tarefas que requerem a CPU antes mesmo da conclusão da execução da função assíncrona.

Como o processador se moveu antes da conclusão da execução da função assíncrona, seu resultado não estará imediatamente disponível. Estará pendente. Se o processador tentasse executar outras partes do programa que dependiam do resultado pendente, obteríamos erros.

  Como se tornar um engenheiro de desenvolvimento de software em teste (SDET)

Portanto, o processador deve executar apenas as partes do programa que não dependem do resultado pendente. Para fazer isso, o JavaScript moderno utiliza promessas.

O que é uma promessa em JavaScript?

Em JavaScript, uma promessa é um valor temporário que uma função assíncrona retorna. As promessas são a espinha dorsal da programação assíncrona moderna em JavaScript.

Depois que uma promessa é criada, uma das duas coisas acontece. Ele resolve quando o valor de retorno da função assíncrona é produzido com sucesso ou rejeita em caso de erro. Esses são eventos dentro do ciclo de vida de uma promessa. Portanto, podemos anexar manipuladores de eventos à promessa a ser chamada quando ela for resolvida ou rejeitada.

Todo o código que requer o valor final de uma função assíncrona pode ser anexado ao manipulador de eventos da promessa para quando for resolvido. Todo o código que manipula o erro de uma promessa rejeitada também será anexado ao seu manipulador de eventos correspondente.

Aqui está um exemplo onde lemos dados de um arquivo no Nodejs.

const fs = require('fs/promises');

fileReadPromise = fs.readFile('./hello.txt', 'utf-8');

fileReadPromise.then((data) => console.log(data));

fileReadPromise.catch((error) => console.log(error));

Na primeira linha, importamos o módulo fs/promises.

Na segunda linha, chamamos a função readFile, passando o nome e a codificação do arquivo cujo conteúdo queremos ler. Esta função é assíncrona; portanto, ele retorna uma promessa. Armazenamos a promessa na variável fileReadPromise.

Na terceira linha, anexamos um ouvinte de evento para quando a promessa for resolvida. Fizemos isso chamando o método then no objeto de promessa. Como argumento para nossa chamada ao método then, passamos a função para executar se e quando a promessa for resolvida.

Na quarta linha, anexamos um ouvinte para quando a promessa for rejeitada. Isso é feito chamando o método catch e passando o manipulador de eventos de erro como um argumento.

Uma abordagem alternativa é usar as palavras-chave async e await. Abordaremos essa abordagem a seguir.

Async e Await explicados

As palavras-chave Async e Await podem ser usadas para escrever Javascript assíncrono de uma maneira que pareça melhor sintaticamente. Nesta seção, explicarei como usar as palavras-chave e que efeito elas têm em seu código.

A palavra-chave await é usada para pausar a execução de uma função enquanto espera a conclusão de uma função assíncrona. Aqui está um exemplo:

const fs = require('fs/promises');

function readData() {
	const data = await fs.readFile('./hello.txt', 'utf-8');

    // This line will not be executed until the data becomes available
	console.log(data);
}

readData()

Usamos a palavra-chave await ao fazer uma chamada para readFile. Isso instruiu o processador a esperar até que o arquivo fosse lido antes que a próxima linha (o console.log) pudesse ser executada. Isso ajuda a garantir que o código que depende do resultado de uma função assíncrona não seja executado até que o resultado esteja disponível.

  Encontre um filme ou programa de TV aleatório para assistir no Netflix, Hulu, HBO Go ou Amazon Prime

Se você tentasse executar o código acima, encontraria um erro. Isso ocorre porque await só pode ser usado dentro de uma função assíncrona. Para declarar uma função como assíncrona, você usa a palavra-chave async antes da declaração da função da seguinte forma:

const fs = require('fs/promises');

async function readData() {
	const data = await fs.readFile('./hello.txt', 'utf-8');

    // This line will not be executed until the data becomes available
	console.log(data);
}

// Calling the function so it runs
readData()

// Code at this point will run while waiting for the readData function to complete
console.log('Waiting for the data to complete')

Ao executar esse trecho de código, você verá que o JavaScript executa o console.log externo enquanto aguarda a disponibilização dos dados lidos do arquivo de texto. Uma vez disponível, o console.log dentro de readData é executado.

O tratamento de erros ao usar as palavras-chave async e await geralmente é executado usando tentar/pegar blocos. Também é importante saber como loop com código assíncrono.

Async e await estão disponíveis em JavaScript moderno. Tradicionalmente, o código assíncrono era escrito por meio do uso de callbacks.

Introdução aos retornos de chamada

Um retorno de chamada é uma função que será chamada assim que o resultado estiver disponível. Todo o código que requer o valor de retorno será colocado dentro do callback. Todo o resto fora do callback não depende do resultado e, portanto, é livre para ser executado.

Aqui está um exemplo que lê um arquivo no Nodejs.

const fs = require("fs");

fs.readFile("./hello.txt", "utf-8", (err, data) => {

	// In this callback, we put all code that requires 
	if (err) console.log(err);
	else console.log(data);
});

// In this part here we can perform all the tasks that do not require the result
console.log("Hello from the program")

Na primeira linha, importamos o módulo fs. Em seguida, chamamos a função readFile do módulo fs. A função readFile lerá o texto de um arquivo que especificamos. O primeiro argumento é qual arquivo é esse e o segundo especifica o formato do arquivo.

A função readFile lê o texto dos arquivos de forma assíncrona. Para fazer isso, ele usa uma função como argumento. Este argumento de função é uma função de retorno de chamada e será chamado assim que os dados forem lidos.

O primeiro argumento passado quando a função callback é chamada é um erro que terá um valor se ocorrer um erro durante a execução da função. Se nenhum erro for encontrado, ele será indefinido.

O segundo argumento passado para o retorno de chamada são os dados lidos do arquivo. O código dentro desta função irá acessar os dados do arquivo. O código fora dessa função não requer dados do arquivo; portanto, pode ser executado enquanto aguarda os dados do arquivo.

  Como inserir um PDF no Excel

A execução do código acima produziria o seguinte resultado:

Principais recursos do JavaScript

Existem alguns recursos e características principais que influenciam o funcionamento do JavaScript assíncrono. Eles estão bem explicados no vídeo abaixo:

Descrevi brevemente as duas características importantes abaixo.

#1. Single-threaded

Ao contrário de outras linguagens que permitem ao programador usar vários threads, o JavaScript permite apenas o uso de um thread. Uma thread é uma sequência de instruções que dependem logicamente umas das outras. Vários threads permitem que o programa execute um thread diferente quando são encontradas operações de bloqueio.

No entanto, vários encadeamentos adicionam complexidade e dificultam a compreensão dos programas que os utilizam. Isso torna mais provável que bugs sejam introduzidos no código e seria difícil depurar o código. JavaScript foi feito single-threaded para simplificar. Como uma linguagem de thread único, ela depende de ser orientada a eventos para lidar com as operações de bloqueio com eficiência.

#2. Orientado a eventos

O JavaScript também é orientado a eventos. Isso significa que alguns eventos acontecem durante o ciclo de vida de um programa JavaScript. Como programador, você pode anexar funções a esses eventos e, sempre que o evento ocorrer, a função anexada será chamada e executada.

Alguns eventos podem resultar da disponibilidade do resultado de uma operação de bloqueio. Nesse caso, a função associada é então chamada com o resultado.

Coisas a considerar ao escrever JavaScript assíncrono

Nesta última seção, mencionarei algumas coisas a serem consideradas ao escrever JavaScript assíncrono. Isso incluirá suporte ao navegador, práticas recomendadas e importância.

Suporte do navegador

Esta é uma tabela que mostra o suporte de promessas em diferentes navegadores.

Fonte: caniuse.com

Esta é uma tabela que mostra o suporte de palavras-chave assíncronas em diferentes navegadores.

Fonte: caniuse.com

Melhores Práticas

  • Sempre opte por async/await, pois ajuda a escrever um código mais limpo e fácil de pensar.
  • Lidar com erros em blocos try/catch.
  • Use a palavra-chave async somente quando for necessário aguardar o resultado de uma função.

Importância do código assíncrono

O código assíncrono permite escrever programas mais eficientes que usam apenas um thread. Isso é importante, pois o JavaScript é usado para criar sites que realizam muitas operações assíncronas, como solicitações de rede e leitura ou gravação de arquivos em disco. Essa eficiência permitiu que runtimes como o NodeJS crescessem em popularidade como o runtime preferido para servidores de aplicativos.

Palavras Finais

Este foi um artigo longo, mas nele pudemos cobrir como as funções assíncronas diferem das funções síncronas regulares. Também abordamos como usar código assíncrono usando apenas promessas, palavras-chave async/await e callbacks.

Além disso, cobrimos os principais recursos do JavaScript. Na última seção, encerramos abordando o suporte do navegador e as práticas recomendadas.

A seguir, confira as perguntas frequentes sobre entrevistas do Node.js.