Introdução à programação assíncrona do Node JS

Introdução à programação assíncrona do Node JS

Domine a programação assíncrona Node.js hoje! Aprimore suas habilidades de desenvolvimento web com nosso guia passo a passo e fácil de entender.

Imagem em destaque

O NodeJS, ao terceirizar as tarefas de desenvolvimento do Node.js, possui uma forma específica de gerenciar suas operações. De acordo com a documentação oficial, é um tempo de execução JavaScript assíncrono orientado a eventos. Mas o que isso significa em termos mais diretos? Aqui, abordarei essa questão e explorarei as práticas recomendadas a serem seguidas ao escrever código assíncrono.

O que é programação assíncrona?

A programação assíncrona significa que parte do código pode ser executada enquanto outras partes do código aguardam uma resposta. Vejamos um exemplo concreto: como desenvolvedor web, você lidará extensivamente com chamadas de API: escreverá um código que envia uma solicitação a um servidor externo e aguarda uma resposta. A questão é que, em primeiro lugar, você nem sabe se obterá a resposta que espera, também não sabe quanto tempo levará para executar aquela função, a situação do servidor que você está chamando , e assim por diante.

Portanto, quando você faz uma solicitação para uma chamada de API, você fica completamente à mercê do estado do servidor, e seu aplicativo deve parar o que quer que esteja fazendo e aguardar a resposta, como acontece com seu usuário aguardando pacientemente o carregamento da página. Certo? Bem, obviamente não. No desenvolvimento web moderno, até milissegundos são contados. Precisamos de uma maneira de fazer nosso aplicativo funcionar sem problemas. Ou seja, quando enviamos uma solicitação para uma API, deveríamos poder fazer outra coisa enquanto esperamos pela resposta. É aí que entra a programação assíncrona.

Existem vários benefícios da programação assíncrona. No momento em que você explorar o mundo da programação assíncrona no NodeJS, você ouvirá termos como loop de eventos, funções de retorno de chamada, promessas, escalabilidade, não bloqueio e assim por diante. Todos esses termos giram em torno da ideia de programação assíncrona, a capacidade de executar código enquanto outras partes do código aguardam uma resposta.

No exemplo acima, estávamos fazendo uma chamada de API hipotética para um servidor externo. Como o NodeJS não é bloqueador, o que significa que nosso código poderá continuar com outras operações enquanto a operação anterior aguarda uma resposta. Isso torna o NodeJS muito escalável porque pode lidar com muitas solicitações simultâneas. O conceito mais importante a ser entendido é que na programação assíncrona, em termos leigos, você pode executar algumas operações enquanto outras operações já estão em execução. Vamos ver isso em ação.

Ciclo de eventos

Em NodeJs, as operações são tratadas de uma forma thread único. Isso significa que apenas uma operação pode ser executada por vez. Mas, como vimos acima, ainda podemos fazer algo enquanto outra coisa está sendo processada.

Como isso é possível? O NodeJS possui um mecanismo chamado loop de eventos que permite ao NodeJS realizar operações de E/S (entrada/saída) sem bloqueio, como um loop que verifica a fila de eventos e executa as operações em ordem. Se você visitar a documentação oficial do NodeJS, aprenderá que o loop de eventos consiste nas seguintes fases: temporizadores, retornos de chamada pendentes, funções de inatividade/preparação, pesquisa, verificação e fechamento de retorno de chamada.

No mesmo link, uma breve visão geral dessas fases é fornecida como tal:

    timers: this phase executes callbacks scheduled by setTimeout  and setInterval .
    pending callbacks: executes I/O callbacks deferred to the next loop iteration.
    idle, prepare: only used internally.
    poll: retrieve new I/O events; execute I/O related callbacks (almost all with the exception of close callbacks, the ones scheduled by timers, and setImmediate ); node will block here when appropriate.
    check: setImmediate  callbacks are invoked here.
    close callbacks: some close callbacks, e.g. socket.on('close', ...).

Embora o loop de eventos seja um tópico bastante complexo que exigiria uma postagem inteira por si só, a coisa mais importante a entender é que o loop de eventos é um mecanismo que permite ao NodeJS realizar operações de E/S sem bloqueio. Isso significa que o NodeJS pode lidar com muitas solicitações simultâneas e é por isso que é tão escalonável. Vamos ver um exemplo de como funciona o loop de eventos.

Como todos os navegadores modernos possuem um mecanismo Javascript, podemos usar o console do navegador para ver como funciona o loop de eventos. Agora, e se abríssemos as ferramentas de desenvolvimento do navegador de nossa escolha e escrevêssemos o seguinte?

console.log("one");
console.log("two");
console.log("three");

O navegador irá registrá-los em ordem, como seria de esperar: um, dois e depois três. Mas vamos considerar outro exemplo:

console.log("one");
setTimeout(  => {
  console.log("two");
}, 1000);
console.log("three");

Algo diferente acontecerá aqui. Devido ao método setTimeout, o navegador registrará um, três e somente um segundo depois, dois. Veja, a função setTimeout é assíncrona aqui. Enquanto o código segue a fila de cima para baixo, quando encontra a função setTimeout, ele não fica parado esperando um segundo para poder executar o código. Não, ele continuará sua execução, e somente após a conclusão da execução irá verificar a fila de eventos e ver se há algum callback a ser executado.

Neste caso, a função setTimeout será executada após um segundo. Este é o exemplo mais básico de como o ciclo de eventos funciona. Este é o mesmo mecanismo que permite ao NodeJS lidar com muitas solicitações simultâneas, tornando-o muito escalável e rápido.

Agora que discutimos o loop de eventos, abordando a escalabilidade e a natureza não bloqueadora do NodeJS, vamos abordar os outros termos comuns que descrevemos acima, como retornos de chamada, promessas e muito mais.

Função de retorno de chamada

Funções de retorno de chamada no NodeJS são funções que são passadas como argumentos para outras funções e são chamadas assim que a função principal é concluída. Um dos exemplos mais básicos que você pode encontrar são os tempos limite. Vejamos um exemplo:

// Example function that performs an asynchronous operation
function fetchData(callback) {
  // Simulate a delay
  setTimeout(  => {
    const data = { name: "John", age: 30 };
    // Call the callback function with the fetched data
    callback(data);
  }, 3000);
}

// Call the fetchData function and pass a callback function
fetchData((data) => {
  console.log(data); // { name: "John", age: 30 }
});

Como o NodeJS é apenas Javascript, podemos usar esse mesmo conceito no console do navegador para entender facilmente como funcionam os retornos de chamada. Portanto, se você colar este código no console do navegador, os dados serão buscados após 3 segundos. Enquanto os dados estão sendo buscados, o restante do código será executado. Assim que os dados forem buscados, a função de retorno de chamada será chamada e os dados serão impressos no console.

A razão pela qual estamos fornecendo uma função setTimeout é para simular uma chamada de API, por exemplo, e como não sabemos quanto tempo levará para a API retornar, precisamos simular esse atraso. Mas os retornos de chamada podem ficar fora de controle. Talvez você já tenha ouvido falar do termo callback hell. Vamos ver o que isso significa.

Inferno de retorno de chamada

Inferno de retorno de chamada é um termo usado para descrever uma situação em que você tem muitos retornos de chamada aninhados. Existe até um site chamado callbackhell.com apenas para explicar esse conceito. Se você visitar essa página, será solicitado o seguinte exemplo:

fs.readdir(source, function (err, files) {
  if (err) {
    console.log("Error finding files: " + err);
  } else {
    files.forEach(function (filename, fileIndex) {
   console.log(filename);
   gm(source + filename).size(function (err, values) {
     if (err) {
       console.log("Error identifying file size: " + err);
     } else {
       console.log(filename + " : " + values);
       aspect = values.width / values.height;
       widths.forEach(
         function (width, widthIndex) {
           height = Math.round(width / aspect);
           console.log(
             "resizing " + filename + "to " + height + "x" + height
           );
           this.resize(width, height).write(
             dest + "w" + width + "_" + filename,
             function (err) {
               if (err) console.log("Error writing file: " + err);
             }
           );
         }.bind(this)
       );
     }
   });
    });
  }
});

São muitos retornos de chamada aninhados. Como você pode ver, nem é tão legível. Gostaríamos de evitar essas coisas em nosso código para nosso próprio bem.

Promessas

Agora, como a assincronicidade é um conceito tão importante no NodeJS, existem algumas maneiras de lidar com isso. Embora um deles sejam retornos de chamada, outra opção é usar promessas. Vamos reescrever o mesmo exemplo setTimeout acima, mas com promessas:

// Example function that returns a Promise
function fetchData  {
  return new Promise((resolve, reject) => {
    // Simulate a delay
    setTimeout(  => {
   const data = { name: "John", age: 30 };
   // Resolve the Promise with the fetched data
   resolve(data);
    }, 1000);
  });
}

// Call the fetchData function and handle the Promise result
fetchData 
  .then((data) => {
    console.log(data); // { name: "John", age: 30 }
  })
  .catch((error) => {
    console.error(error);
  });

Desta forma parece um pouco mais limpo. Pelo menos podemos lidar com possíveis erros. Lembre-se de que não temos ideia do estado do servidor para o qual estamos fazendo a solicitação. Não há garantia de que nossa solicitação retornará com a resposta que esperamos. Portanto, precisamos de uma maneira de lidar com possíveis erros. A instrução catch aqui é uma boa maneira de fazer isso. Mas há também uma ressalva aqui: talvez não estejamos mais no inferno do retorno de chamada, mas podemos facilmente cair nas profundezas de outro inferno que é chamado de inferno da promessa, ou “inferno do então”. Aqui, “then hell” leva o nome de ter muitas instruções then encadeadas no código. Vamos ver o que isso significa.

Prometa Inferno

Inferno de promessas é um termo usado para descrever uma situação em que você tem muitas promessas aninhadas. Vamos ver um exemplo:

getData 
  .then(function (data) {
    return processData(data)
   .then(function (processedData) {
     return saveData(processedData)
       .then(function (savedData) {
         return displayData(savedData);
       })
       .catch(function (err) {
         console.log("Error while saving data:", err);
       });
   })
   .catch(function (err) {
     console.log("Error while processing data:", err);
   });
  })
  .catch(function (err) {
    console.log("Error while getting data:", err);
  });

Isso não é melhor do que retornos de chamada. Então, qual é a solução? Uma maneira é usar async/await. Veja como isso funciona.

Funções assíncronas (aguardar)

Async/await é uma maneira de lidar com código assíncrono de maneira síncrona. É de longe o meu favorito e ajuda a evitar as armadilhas de uma função de retorno de chamada e promessas. Vamos ver como isso funciona:

async function fetchData  {
  try {
    const res = await fetch("
    const data = await res.json ;
    console.log(data);
    return data;
  } catch (error) {
    console.error(error);
  }
}

Aqui, em vez de setTimeout, estamos fazendo uma solicitação de API real. Para referência, jsonplaceholder é uma API fictícia que podemos chamar para testar solicitações de API. A solicitação acima deve retornar uma lista de usuários. Agora, esse método de programação assíncrona funciona da seguinte maneira: A função deve ser chamada de assíncrona para que possamos usar a palavra-chave await.

A palavra-chave await é o momento em que esperamos o término da operação assíncrona. Quando terminar, podemos continuar com o restante do nosso código. Você também verá que usamos instruções try/catch, que são extremamente úteis neste caso específico: Podemos lidar com erros de uma forma muito mais fácil graças a este método.

Trabalhando com código assíncrono em NodeJS

Adotar o poder da programação assíncrona no NodeJS não apenas agiliza nosso código, mas também melhora significativamente o desempenho e a capacidade de resposta de nossos aplicativos.

Criando um Servidor Expresso Básico

Agora, veja como podemos usar programação assíncrona em NodeJS com um exemplo. Estaremos criando um servidor expresso básico e enviaremos uma solicitação get para a API jsonplaceholder. Estaremos usando o método async/await para isso, pois é a maneira mais conveniente. Para prosseguir, você precisará do node e do npm instalados em seu sistema.

Primeiro, criamos uma nova pasta chamada 'async-node' e fazemos cd nela. Observe que estamos usando o terminal Linux, então usamos o comando mkdir para criar a pasta. Quando estou na pasta, executo npm init -y para inicializar um novo projeto npm.

O sinalizador -y serve para pular as perguntas que o npm nos faz. Feito isso, instalarei os pacotes express e axios via npm. Também usarei o nodemon neste projeto, mas não irei instalar porque o tenho instalado globalmente. Para facilitar o uso, recomendo que você faça o mesmo. Se não quiser, você também pode instalá-lo localmente como uma dependência de desenvolvimento.

Para instalar o express e o axios, executamos o seguinte comando:

npm eu expresso axios

Para o nodemon, se você quisesse instalá-lo globalmente, você escreveria npm i -g nodemon. Observe também que aqui você pode precisar fornecer acesso root especificando sudo. Se eu quisesse instalar o nodemon localmente como uma dependência de desenvolvimento, eu executaria npm i –save-dev nodemon

Agora que instalei os pacotes, posso criar um arquivo index.js com o comando touch index.js e começar a codificar. Vou abrir o projeto com VsCode via code . comando. Quando estiver lá, irei para o arquivo package.json e farei algumas alterações lá. Adicionarei a seguinte linha à seção de scripts:

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node index.js",
    "dev": "nodemon index.js"
  },

Agora, sempre que executarmos npm run start ou npm run dev, ele executará o arquivo index.js com os pacotes especificados. Agora que terminamos com o arquivo package.json, vamos para o arquivo index.js e o preenchemos com um servidor expresso básico:

const express = require("express");

const app = express ;

app.get(" (req, res) => {
  res.send("Hello, world!");
});

app.listen(3000,   => {
  console.log("Server listening on port 3000");
});

Aqui algumas coisas estão acontecendo. Em primeiro lugar, estou importando o pacote expresso e inicializando-o via const app = express ;. Em seguida, estamos criando uma solicitação get para a rota root e enviando uma resposta com o texto “Hello, world!”. Por fim, estamos iniciando o servidor na porta 3000. Agora, se eu executar npm run dev e for para localhost:3000, se tudo estiver correto, deveremos ver o texto “Hello, world!”.

Envie solicitação com Axios e escreva a resposta em um arquivo

Mas queremos fazer mais do que apenas criar um servidor básico. Queremos enviar uma solicitação get para uma API externa e gravá-la em um arquivo. Então vamos fazer isso. Verifique o seguinte trecho de código:

const axios = require("axios");
const fs = require("fs");
const express = require("express");

const app = express ;

app.get(" async (req, res) => {
  try {
    // Make API call using Axios
    const response = await axios.get(
   "
    );
    debugger;

    // Write data to file using asynchronous file system method
    await fs.promises.writeFile("response.txt", JSON.stringify(response.data));

    res.send("Data written to file successfully!");
  } catch (error) {
    console.error(error);
    res.status(500).send("An error occurred");
  }
});

app.listen(3000,   => console.log("Listening on port 3000"));

Neste código, importamos Axios e fs junto com express. Axios nos permite fazer solicitações de API, enquanto fs dá acesso ao sistema de arquivos para ler e gravar arquivos.

Após inicializar o aplicativo Express, adicionamos um manipulador de rota GET para a rota raiz. Observe a palavra-chave async – isso significa que a rota contém código assíncrono.

Dentro do manipulador, usamos um bloco try/catch para lidar normalmente com quaisquer erros. Na parte try, fazemos uma solicitação GET para a API fictícia usando axios e aguardamos uma chamada para fs.promises para criar um novo arquivo chamado response.txt. Este arquivo conterá os dados de resposta da API.

Quando executamos o servidor com npm run dev e visitamos localhost:3000, esperamos ver a mensagem “Dados gravados no arquivo com sucesso!” mensagem. Mas em vez disso, recebemos um erro.

Claramente, há um bug que impede que o arquivo seja gravado corretamente. Agora é hora de depurar nosso código assíncrono para descobrir e corrigir o problema. Usaremos o depurador integrado do Node junto com outras técnicas para rastrear o fluxo de execução e identificar problemas.

Depurar código assíncrono como esse requer algumas habilidades especializadas, mas aprender essas habilidades nos permitirá construir aplicativos Node complexos com confiança.

Depurando Nodejs com ferramentas de desenvolvimento do Chrome

A depuração é uma habilidade crítica para desenvolvedores. Quando aparecem bugs, precisamos de ferramentas para identificar a causa raiz. Neste exemplo, nosso código encontra um erro em vez de gravar os dados conforme esperado. Felizmente, o Node.js tem um depurador integrado que podemos aproveitar.

Para usá-lo, primeiro feche o servidor e execute nodemon --inspect index.js. Isso reinicia o servidor no modo de inspeção. Em seguida, abra o Chrome e encontre o painel do depurador do Node usando o ícone verde do Node.js.

Na aba Fontes, vemos a linha 10 destacada onde nossa solicitação Axios é feita. Isso indica que o erro ocorre aqui. Podemos pausar a execução na linha 10 e inspecionar variáveis ​​para obter mais contexto.

Verificando o endpoint da API, identificamos o problema – há um caminho “/todos/-1” inválido. Eu corrijo isso para “/todos/1” e retomo a execução. Agora no depurador, vemos um código de status 200 na solicitação – sucesso!

O depurador foi inestimável para rastrear o fluxo de execução e identificar a causa raiz. Agora com o bug corrigido, reiniciamos normalmente com npm run dev. Visitando localhost, obtemos corretamente a mensagem “Dados gravados” e um arquivo response.txt contendo os dados da API.

Ser capaz de depurar código Node assíncrono é crucial para qualquer desenvolvedor. Dominar o depurador integrado e outros fluxos de trabalho de depuração lhe dará as habilidades para eliminar bugs com eficiência em seus aplicativos.

Conclusão

Aqui, discutimos a programação assíncrona em NodeJs. Abordamos algumas maneiras de lidar com a assincronicidade, como promessas, funções de retorno de chamada e o relativamente novo bloco async/await. Também examinamos como depurar nosso código usando o depurador integrado do nodeJS. Portanto, agora cabe a você incorporar e escrever código assíncrono em seus projetos futuros. No entanto, se você precisar de mais assistência ou desejar dimensionar seu projeto, considere contratar uma empresa de desenvolvimento Node.js de boa reputação.

Perguntas frequentes

Qual é a diferença entre programação assíncrona e síncrona?

A programação síncrona é uma abordagem linear onde as tarefas são executadas uma após a outra, o que significa que uma tarefa deve ser concluída antes que a próxima possa começar. Isso pode tornar seu programa mais fácil de entender, mas também significa que ele pode travar ou parar de responder se uma tarefa demorar muito para ser concluída.

Uma função assíncrona, por outro lado, permite que tarefas sejam executadas simultaneamente. Se uma tarefa demorar muito para ser concluída (como ler um arquivo de um disco ou buscar dados da rede), o programa poderá continuar com outras tarefas. Depois que a tarefa longa é concluída, uma função de retorno de chamada normalmente é invocada para tratar o resultado. A natureza sem bloqueio das operações assíncronas as torna particularmente adequadas para a execução de tarefas dentro do código JavaScript que exigem espera por recursos externos ou precisam ser executadas em segundo plano. Esta abordagem pode levar à criação de programas potencialmente mais eficientes e responsivos.

Como funciona o loop de eventos no Node.js para lidar com operações assíncronas?

O loop de eventos no Node.js é o mecanismo que lida com operações assíncronas. Quando você contrata desenvolvedores Node.js, eles aproveitam o loop de eventos iniciando tarefas assíncronas, como ler arquivos ou fazer solicitações de banco de dados. Essas tarefas são executadas fora do loop de eventos, permitindo continuar processando outro código JavaScript. Quando uma tarefa assíncrona é concluída, sua função de retorno de chamada é adicionada a uma fila, ou 'fila de retorno de chamada'. O loop de eventos verifica essa fila e processa os retornos de chamada um por um, manipulando assim os resultados das operações assíncronas de forma eficiente.

Fonte: BairesDev

Powrót do blogu

Zostaw komentarz

Pamiętaj, że komentarze muszą zostać zatwierdzone przed ich opublikowaniem.