Filas de Promises e processamento em lotes de tarefas concorrentes no Deno
25 de setembro de 2024
0 minutos de leituraO que é Deno?
Deno é um ambiente de execução seguro para JavaScript e TypeScript, criado com o mesmo mecanismo JavaScript V8 do Node.js, mas com melhorias significativas. Foi desenvolvido por Ryan Dahl, criador original do Node.js, para resolver alguns dos principais problemas e limitações que ele identificou no Node.js ao longo do tempo. Deno foi projetado para reforçar a segurança, simplificar o gerenciamento de módulos e oferecer suporte a TypeScript desde o início.
Veja como o servidor responde às solicitações com a string "Hello World." A instrução import busca o método serve diretamente de uma URL, no módulo de servidor HTTP da biblioteca padrão do Deno. Esse é um dos aspectos únicos do Deno: ele busca e armazena em cache os módulos importados de URLs da web, eliminando a necessidade de um gerenciador de pacotes como o npm.
Como lidar com tarefas concorrentes no Deno
Lidar com tarefas concorrentes é essencial em qualquer framework ou runtime moderno para web, como o Deno. Em JavaScript, operações assíncronas são comuns, seja para buscar dados em um banco de dados, fazer solicitações HTTP ou ler arquivos do disco.
Como JavaScript é uma linguagem de thread única, ele depende de sua natureza orientada a eventos para gerenciar essas tarefas simultaneamente sem bloquear a thread principal.
O exemplo acima mostra como JavaScript lida com tarefas concorrentes usando Promise.all(). Ao executar várias promises simultaneamente, Promise.all() aguarda a conclusão de todas e, então, retorna os resultados. Isso é ótimo para otimizar o desempenho, mas tem desvantagens. Se uma promise falhar, todas as outras também serão rejeitadas, o que pode levar a um alto consumo de memória quando há muitas tarefas.
No Deno, podemos usar filas de Promises e processamento de tarefas em lotes para gerenciar a concorrência com mais eficiência. Assim, é possível lidar melhor com um grande número de tarefas sem sobrecarregar a memória nem correr o risco de uma falha total se uma única tarefa apresentar erro.
Vamos explicar como essas técnicas de filas e processamento de tarefas em lotes podem ajudar você a escrever código mais eficiente, robusto e com melhor desempenho.
Entenda as filas de Promises e as tarefas concorrentes
As filas de Promises são essenciais na programação assíncrona em JavaScript. Entender as filas de Promises e a concorrência pode aumentar significativamente a eficiência e o desempenho das suas aplicações.
Uma abordagem sequencial para tarefas assíncronas
Uma fila de Promises é uma lista de objetos Promise aguardando para serem resolvidos ou rejeitados. Em JavaScript, uma "Promise" é um objeto que representa a conclusão ou falha futura de uma operação assíncrona.
Uma fila de Promises ajuda a gerenciar várias promises que precisam ser executadas em uma ordem específica. Ela garante que as promises sejam resolvidas sequencialmente, evitando possíveis problemas causados pela execução de código assíncrono.
Veja um exemplo de uma fila simples de Promises em ação:
No exemplo acima, Promise.all recebe um iterável de promises e retorna uma nova promise que é cumprida com um array dos valores cumpridos das promises recebidas, na mesma ordem. Se alguma promise for rejeitada, a promise retornada também será rejeitada com o motivo da primeira rejeição.
O que é concorrência em JavaScript?
No contexto do JavaScript, concorrência é a capacidade do mecanismo JavaScript de executar vários trechos de código (tarefas) sem bloquear a execução de outras tarefas. Vale lembrar que JavaScript é principalmente uma linguagem de thread única, ou seja, só pode realizar uma operação por vez no código executado pelo usuário. No entanto, com a ajuda da programação assíncrona, como promises, JavaScript pode agendar tarefas para serem executadas simultaneamente.
A concorrência em JavaScript pode melhorar significativamente o desempenho, especialmente em operações de E/S, como solicitações de rede, operações no sistema de arquivos ou qualquer operação que exija algum tempo de espera.
Veja um exemplo de tarefas concorrentes em JavaScript:
Neste exemplo, task1 e task2 são executadas simultaneamente. Embora task1 leve mais tempo para terminar, ela não bloqueia a execução de task2.
Este exemplo de código pode parecer semelhante ao anterior, mas, nos exemplos anteriores de código JavaScript, usamos Promise.resolve(1), que era resolvida imediatamente e retornava o número inteiro como valor. Este exemplo de execução concorrente mostra como as tarefas podem ser agendadas para serem executadas simultaneamente em uma thread em segundo plano.
Situações reais em que filas de Promises e concorrência são essenciais
Filas de Promises e concorrência estão presentes em toda parte no JavaScript. Por exemplo, em uma aplicação web, talvez seja necessário buscar dados de vários endpoints simultaneamente e, em seguida, processá-los sequencialmente, em uma ordem específica. Esse é um exemplo perfeito do uso de filas de Promises e concorrência.
Considere um sistema online de compra de ingressos de cinema. Quando alguém faz uma reserva, o sistema pode precisar executar várias tarefas, como verificar a disponibilidade de assentos, reservar o assento, processar o pagamento e enviar um e-mail de confirmação. As filas de Promises podem gerenciar essas tarefas e garantir que sejam executadas na ordem correta. Já algumas tarefas, como enviar e-mails de confirmação para várias pessoas, podem ser realizadas simultaneamente para aumentar a eficiência.
Entender e aplicar os conceitos de filas de Promises e concorrência em JavaScript pode melhorar muito o desempenho e a eficiência das suas aplicações. À medida que você se aprofunda em JavaScript, esses conceitos serão cada vez mais úteis no desenvolvimento de aplicações complexas e de alto desempenho.
Criando uma aplicação Deno para executar solicitações HTTP com fetch
Vamos ver como criar uma aplicação básica em Deno capaz de enviar várias solicitações HTTP com fetch em lote. Para isso, usaremos o método Promise.allSettled(). Nossa aplicação buscará dados de pacotes no endpoint JSON do registro npm.
Você pode acompanhar este tutorial passo a passo. Se precisar consultar o código-fonte completo para reproduzir os detalhes da aplicação Deno apresentados neste artigo, acesse o repositório do GitHub: https://github.com/snyk-snippets/deno-promise-queues-batch-queue
Configurando o ambiente Deno
O primeiro passo para criar nossa aplicação é configurar o ambiente Deno. Deno é um runtime seguro para JavaScript e TypeScript, baseado no mecanismo JavaScript V8 e na linguagem de programação Rust. Para instalar o Deno, use o seguinte comando:
Quando a instalação terminar, verifique se tudo deu certo executando o seguinte comando:
Esse comando exibirá a versão instalada do Deno.
Escrevendo um programa simples em Deno
Nosso programa em Deno enviará várias solicitações fetch em lote usando o método Promise.allSettled().
Promise.allSettled() é um método que retorna uma promise resolvida depois que todas as promises fornecidas forem resolvidas ou rejeitadas. O resultado é um array de objetos, cada um descrevendo o desfecho de uma promise. Esse método é especialmente útil quando a ordem em que as promises são concluídas não importa e você só precisa saber quando todas tiverem terminado.
Qual é a diferença entre Promise.all() e Promise.allSettled() ?
Promise.all() é adequado quando você precisa que todas as promises sejam cumpridas e quer interromper a operação assim que uma delas for rejeitada. Já Promise.allSettled() é útil quando você quer aguardar a conclusão de todas as promises, independentemente do resultado.
Buscando dados de pacotes npm no endpoint JSON do registro npm
Nossa aplicação buscará dados de pacotes no endpoint JSON do registro npm. O registro npm é um grande banco de dados de pacotes JavaScript e disponibiliza uma API pública que permite buscar dados de pacotes em formato JSON. Isso atende às nossas necessidades, pois facilita o processamento dos dados obtidos.
Exemplo de código do programa Deno
Veja um exemplo de como escrever um programa em Deno que envia várias solicitações HTTP em lote usando Promise.allSettled().
Neste exemplo, criamos um array de URLs que queremos acessar. Em seguida, percorremos esse array e enviamos uma solicitação fetch para cada URL. Usamos Promise.allSettled() para aguardar a conclusão de todas as solicitações fetch e, então, processamos os resultados.
Uma resposta parcial do programa Deno acima:
Aprimorando a aplicação Deno com o módulo BatchQueue
O módulo BatchQueue no Deno oferece um mecanismo para gerenciar e controlar a execução de tarefas na sua aplicação Deno. É uma ferramenta poderosa que permite limitar o número de tarefas executadas simultaneamente. Isso pode ser útil ao trabalhar com recursos sujeitos a limites de uso ou ao lidar com tarefas que exigem muito da CPU ou da memória.
Em solicitações HTTP, é melhor agir com responsabilidade e limitar o número de solicitações simultâneas enviadas a um serviço remoto, como o registro npm, para evitar saturá-lo e sobrecarregá-lo. Limitar as solicitações simultâneas também ajuda a evitar que seu programa seja identificado como uma possível tentativa de negação de serviço.
O BatchQueue no Deno permite criar uma fila de promises e executá-las em lotes. Essa técnica pode melhorar significativamente o desempenho da sua aplicação Deno, evitando a sobrecarga dos recursos do sistema e aumentando a capacidade de processamento das tarefas.
Aprimorando o programa Deno para usar uma fila com baixa concorrência
Vamos considerar um programa Deno que faz várias solicitações HTTP simultaneamente. Por padrão, o Deno tentará executar todas essas tarefas ao mesmo tempo. No entanto, isso pode causar problemas se houver tarefas suficientes para consumir todos os recursos disponíveis do sistema ou se elas interagirem com sistemas externos que tenham limites de uso.
Com o módulo BatchQueue, podemos controlar o número de tarefas simultâneas. Veja um exemplo simplificado de como usar BatchQueue em um programa Deno:
Neste exemplo, o BatchQueue está configurado com uma concurrency de 5, o que significa que somente cinco tarefas poderão ser executadas simultaneamente.
Cada tarefa é adicionada à fila do BatchQueue, e a função makeHttpRequest é chamada quando a tarefa sai da fila para ser executada. O BatchQueue garante que apenas cinco tarefas sejam executadas ao mesmo tempo.
Fila de Promises versus Promise.all()
Ao comparar a versão aprimorada do programa Deno, que usa BatchQueue, com a versão inicial, você verá uma melhora significativa no uso dos recursos do sistema e no desempenho geral da sua aplicação Deno.
O mecanismo BatchQueue garante que o programa Deno não esgote todos os recursos disponíveis do sistema, algo comum ao executar um grande número de tarefas simultaneamente.
Com o módulo BatchQueue no Deno, você pode gerenciar com eficiência a execução das tarefas na sua aplicação e melhorar o desempenho e a capacidade de resposta dos seus programas Deno.
Adicionando ProgressBar à aplicação Deno
Adicionar feedback à sua aplicação Deno pode melhorar muito a experiência do usuário. Uma das maneiras mais intuitivas de mostrar esse feedback em uma CLI é usar uma barra de progresso.
Um desses módulos é o ProgressBar, que simplifica a adição de uma barra de progresso à sua aplicação Deno.
O módulo ProgressBar é um pacote leve e altamente personalizável que oferece uma barra de progresso fácil de integrar às suas aplicações. Ele conta com uma API simples, que facilita a implementação, além de opções de personalização para adaptar a aparência da barra à sua aplicação.
Para instalar o módulo ProgressBar, importe-o para a sua aplicação desta forma:
Adicionando ProgressBar para exibir o progresso das solicitações concluídas
Depois de importar o módulo ProgressBar, você pode usá-lo para mostrar visualmente o progresso das solicitações concluídas na sua aplicação Deno.
Você pode criar uma nova instância de ProgressBar e especificar o número total de etapas que a barra deve representar. Depois, para cada solicitação concluída, chame o método .tick() para avançar a barra de progresso.
Veja um exemplo básico de como usar ProgressBar para acompanhar e exibir o progresso das solicitações concluídas:
Neste exemplo, a barra de progresso avançará uma etapa cada vez que uma solicitação for concluída. Você pode personalizar a aparência da barra ajustando as opções complete e incomplete ao criar a instância de ProgressBar.
Exemplo de código do programa Deno com ProgressBar
Por fim, vamos ver um exemplo mais elaborado de integração do ProgressBar em uma situação mais próxima do mundo real. Suponha que temos um lote de solicitações de API para enviar e queremos acompanhar o progresso.
Neste exemplo, simulamos o envio de solicitações de API e usamos o ProgressBar para acompanhar o progresso. Cada chamada a sendRequest() representa uma solicitação enviada e uma resposta aguardada. A cada solicitação concluída, avançamos a barra de progresso chamando progressBar.tick().
Ao executar nosso programa Deno completo, com os módulos Promises Batch Queue e ProgressBar, obtemos a seguinte saída na linha de comando:
E pronto! Você adicionou uma barra de progresso ao seu aplicativo Deno para exibir o avanço das solicitações concluídas. O módulo ProgressBar é um ótimo complemento para seus aplicativos Deno, pois oferece uma maneira intuitiva de acompanhar o status de tarefas demoradas. Experimente no seu próximo projeto Deno!
Lembre-se: o aplicativo Deno completo deste exemplo de Promise Batch Queue está disponível em: https://github.com/snyk-snippets/deno-promise-queues-batch-queue
Segurança em aplicativos Deno: vulnerabilidade SSRF
A cibersegurança é uma preocupação fundamental em qualquer projeto de desenvolvimento de software. No ecossistema Deno, um possível risco de segurança ao qual os desenvolvedores precisam ficar atentos é a vulnerabilidade de falsificação de solicitação do lado do servidor (SSRF).
Esta seção explora a vulnerabilidade SSRF, os riscos que ela representa para aplicativos Deno e maneiras de mitigá-la. Também apresentaremos um exemplo prático de código para um programa Deno seguro.
O que é uma vulnerabilidade SSRF?
SSRF é um tipo de vulnerabilidade que permite a um invasor manipular um aplicativo do lado do servidor para realizar interações de rede não autorizadas. Ela representa um risco porque possibilita que o invasor interaja com sistemas internos, o que pode levar a acessos não autorizados ou vazamento de dados. Em essência, uma vulnerabilidade SSRF pode transformar o servidor em um proxy para as atividades maliciosas do invasor.
Vulnerabilidade SSRF em aplicativos Deno
O Deno foi projetado para ser seguro por padrão: todos os scripts são executados em uma sandbox segura, a menos que o usuário conceda permissões explicitamente. No entanto, quando as permissões necessárias são concedidas, os aplicativos Deno podem ficar expostos a vulnerabilidades SSRF.
Por exemplo, se um aplicativo Deno receber a permissão "allow-net", poderá fazer solicitações de rede a qualquer host. Um invasor pode explorar isso injetando um host malicioso na solicitação, causando uma vulnerabilidade SSRF.
Em geral, os desenvolvedores podem executar aplicativos Deno, como o programa de fila de Promises que escrevemos, da seguinte forma:
Como mitigar vulnerabilidades SSRF em aplicativos Deno
Para mitigar possíveis vulnerabilidades SSRF, os desenvolvedores precisam adotar práticas seguras de programação, incluindo:
Princípio do menor privilégio: conceda apenas as permissões mínimas necessárias para que o aplicativo Deno funcione corretamente. Se o aplicativo não precisar fazer solicitações de rede, não conceda a permissão
--allow-net(ou sua forma abreviada, a flag de linha de comando-A).Validação de entrada: valide corretamente todas as entradas do usuário e rejeite as suspeitas ou malformadas. Em geral, os usuários não devem fornecer URLs absolutas. Limite-os às URLs relativas hospedadas e permitidas pelo seu aplicativo.
Exemplo de código de um programa Deno seguro
Vamos ver um aplicativo Deno básico com medidas de segurança para ajudar a mitigar vulnerabilidades SSRF.
Neste exemplo de código, servimos um servidor HTTP na porta 8000. Quando uma solicitação é recebida, verificamos o nome do host na URL da solicitação. Se não for 'localhost' nem '127.0.0.1', respondemos com a mensagem "Unauthorized host", impedindo assim solicitações a hosts externos.
Ao adotar práticas seguras de programação e ter cuidado com as permissões concedidas aos aplicativos Deno, podemos mitigar com eficácia o risco de vulnerabilidades SSRF.
A importância de gerenciar tarefas simultâneas com eficiência no Deno
Gerenciar tarefas simultâneas com eficiência é essencial para desenvolver aplicativos de alto desempenho. No Deno, isso é ainda mais importante, pois ele foi criado para lidar com código moderno em JavaScript e TypeScript, que costuma envolver muitas operações assíncronas.
Com filas de Promises e processamento em lotes, podemos gerenciar recursos com eficiência e evitar problemas como estouro de memória ou limitação da CPU. Isso melhora a confiabilidade e a capacidade de resposta do aplicativo, aprimorando a experiência geral do usuário.
Explore o Deno e seus módulos
O Deno oferece muito mais do que filas de Promises e processamento de tarefas simultâneas em lotes. É um runtime poderoso e flexível, com diversos recursos e módulos que ajudam a criar aplicativos robustos, seguros e eficientes.
Por exemplo, a biblioteca padrão do Deno oferece um conjunto de módulos de alta qualidade e revisados, compatíveis com o Deno. Ela inclui módulos para manipulação de datas e horas, hashing, servidores HTTP, testes e muito mais.
Com foco em segurança, o Deno também permite controlar de forma granular o que um aplicativo pode acessar, algo essencial diante das ameaças à cibersegurança. Ainda assim, muitas vulnerabilidades, como a SSRF, exigem intervenção específica e controles de segurança implementados pelos desenvolvedores.
Para entender melhor o Deno e como usá-lo, leia a documentação oficial do Deno. Lá, você encontrará informações sobre vários recursos e módulos do Deno, fundamentais para entender as filas de Promises e o processamento de tarefas simultâneas em lotes.
Para garantir a segurança do seu aplicativo, é fundamental entender as diferentes ameaças à segurança de aplicativos, incluindo as vulnerabilidades de falsificação de solicitação do lado do servidor (SSRF). Confira estes recursos adicionais para entender e mitigar vulnerabilidades SSRF:
Vulnerabilidades de confusão de URL em cenários reais: inconsistências entre parsers
Validação segura de URLs em JavaScript- Uma análise técnica da violação causada pela configuração incorreta da nuvem da Capital One
Proteja seu código enquanto desenvolve
O Snyk analisa seu código em busca de problemas de qualidade e segurança e recomenda correções direto no seu IDE.



