Domine a iteração assíncrona em JavaScript com 'for await...of' e auxiliares personalizados. Melhore o processamento de fluxos de dados com exemplos práticos.
Auxiliar de Iterador Assíncrono JavaScript: For Each - Iteração de Processamento de Fluxo
A programação assíncrona é um pilar do desenvolvimento JavaScript moderno, permitindo que as aplicações lidem com operações demoradas sem bloquear a thread principal. Os iteradores assíncronos, introduzidos no ECMAScript 2018, fornecem um mecanismo poderoso para processar fluxos de dados de forma assíncrona. Esta postagem de blog explora o conceito de iteradores assíncronos e demonstra como implementar uma função auxiliar 'for each' assíncrona para otimizar o processamento de fluxos.
Entendendo Iteradores Assíncronos
Um iterador assíncrono é um objeto que está em conformidade com a interface AsyncIterator. Ele define um método next() que retorna uma promessa (promise), que resolve para um objeto com duas propriedades:
value: O próximo valor na sequência.done: Um booleano indicando se o iterador foi concluído.
Iteradores assíncronos são comumente usados para consumir dados de fontes assíncronas como fluxos de rede, sistemas de arquivos ou bancos de dados. O loop for await...of fornece uma sintaxe conveniente para iterar sobre iteráveis assíncronos.
Exemplo: Lendo um Arquivo de Forma Assíncrona
Considere um cenário onde você precisa ler um arquivo grande linha por linha sem bloquear a thread principal. Você pode conseguir isso usando um iterador assíncrono:
const fs = require('fs');
const readline = require('readline');
async function* readFileLines(filePath) {
const fileStream = fs.createReadStream(filePath);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
for await (const line of rl) {
yield line;
}
}
async function processFile(filePath) {
for await (const line of readFileLines(filePath)) {
console.log(`Line: ${line}`);
}
}
// Exemplo de uso
processFile('path/to/your/file.txt');
Neste exemplo, readFileLines é uma função geradora assíncrona que produz (yields) cada linha do arquivo à medida que é lida. A função processFile então itera sobre as linhas usando for await...of, processando cada linha de forma assíncrona.
Implementando um Auxiliar 'For Each' Assíncrono
Embora o loop for await...of seja útil, ele pode se tornar verboso quando você precisa realizar operações complexas em cada elemento do fluxo. Uma função auxiliar 'for each' assíncrona pode simplificar esse processo encapsulando a lógica de iteração.
Implementação Básica
Aqui está uma implementação básica de uma função 'for each' assíncrona:
async function asyncForEach(iterable, callback) {
for await (const item of iterable) {
await callback(item);
}
}
Esta função recebe um iterável assíncrono e uma função de callback como argumentos. Ela itera sobre o iterável usando for await...of e chama a função de callback para cada item. A função de callback também deve ser assíncrona se você quiser aguardar sua conclusão antes de passar para o próximo item.
Exemplo: Processando Dados de uma API
Suponha que você esteja buscando dados de uma API que retorna um fluxo de itens. Você pode usar o auxiliar 'for each' assíncrono para processar cada item à medida que ele chega:
async function* fetchDataStream(url) {
const response = await fetch(url);
const reader = response.body.getReader();
const decoder = new TextDecoder();
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
return;
}
// Assumindo que a API retorna pedaços (chunks) de JSON
const chunk = decoder.decode(value);
const items = JSON.parse(`[${chunk.replace(/\}\{/g, '},{')}]`); //Divide os pedaços em um array JSON válido
for(const item of items){
yield item;
}
}
} finally {
reader.releaseLock();
}
}
async function processItem(item) {
// Simula uma operação assíncrona
await new Promise(resolve => setTimeout(resolve, 100));
console.log(`Processing item: ${JSON.stringify(item)}`);
}
async function main() {
const apiUrl = 'https://api.example.com/data'; // Substitua pelo endpoint da sua API
await asyncForEach(fetchDataStream(apiUrl), processItem);
console.log('Finished processing data.');
}
// Exemplo de uso
main();
Neste exemplo, fetchDataStream busca dados da API e produz cada item à medida que é recebido. A função processItem simula uma operação assíncrona em cada item. O auxiliar asyncForEach então simplifica a lógica de iteração e processamento.
Melhorias e Considerações
Tratamento de Erros
É crucial tratar os erros que podem ocorrer durante a iteração assíncrona. Você pode envolver a função de callback em um bloco try...catch para capturar e tratar exceções:
async function asyncForEach(iterable, callback) {
for await (const item of iterable) {
try {
await callback(item);
} catch (error) {
console.error(`Error processing item: ${item}`, error);
// Você pode escolher relançar o erro ou continuar o processamento
}
}
}
Controle de Concorrência
Por padrão, o auxiliar 'for each' assíncrono processa os itens sequencialmente. Se você precisar processar itens simultaneamente, pode usar um pool de Promises para limitar o número de operações concorrentes:
async function asyncForEachConcurrent(iterable, callback, concurrency) {
const executing = [];
for await (const item of iterable) {
const p = callback(item).then(() => executing.splice(executing.indexOf(p), 1));
executing.push(p);
if (executing.length >= concurrency) {
await Promise.race(executing);
}
}
await Promise.all(executing);
}
async function processItem(item) {
// Simula uma operação assíncrona
await new Promise(resolve => setTimeout(resolve, 100));
console.log(`Processing item: ${JSON.stringify(item)}`);
}
async function main() {
const apiUrl = 'https://api.example.com/data'; // Substitua pelo endpoint da sua API
await asyncForEachConcurrent(fetchDataStream(apiUrl), processItem, 5); // Concorrência de 5
console.log('Finished processing data.');
}
Neste exemplo, asyncForEachConcurrent limita o número de execuções de callback concorrentes ao nível de concorrência especificado. Isso pode melhorar o desempenho ao lidar com grandes fluxos de dados.
Cancelamento
Em alguns casos, você pode precisar cancelar o processo de iteração prematuramente. Você pode conseguir isso usando um AbortController:
async function asyncForEach(iterable, callback, signal) {
for await (const item of iterable) {
if (signal && signal.aborted) {
console.log('Iteration aborted.');
return;
}
await callback(item);
}
}
async function main() {
const controller = new AbortController();
const signal = controller.signal;
setTimeout(() => {
controller.abort(); // Aborta após 2 segundos
}, 2000);
const apiUrl = 'https://api.example.com/data'; // Substitua pelo endpoint da sua API
await asyncForEach(fetchDataStream(apiUrl), processItem, signal);
console.log('Finished processing data.');
}
Neste exemplo, a função asyncForEach verifica a propriedade signal.aborted antes de cada iteração. Se o sinal for abortado, a iteração é interrompida.
Aplicações no Mundo Real
Iteradores assíncronos e o auxiliar 'for each' assíncrono podem ser aplicados a uma ampla gama de cenários do mundo real:
- Pipelines de processamento de dados: Processamento de grandes conjuntos de dados de bancos de dados ou sistemas de arquivos.
- Fluxos de dados em tempo real: Manipulação de dados de web sockets, filas de mensagens ou redes de sensores.
- Consumo de API: Busca e processamento de dados de APIs que retornam fluxos de itens.
- Processamento de imagem e vídeo: Processamento de grandes arquivos de mídia em pedaços (chunks).
- Análise de logs: Análise de grandes arquivos de log linha por linha.
Exemplo - Dados de Ações Internacionais: Considere uma aplicação que busca cotações de ações em tempo real de várias bolsas internacionais. Um iterador assíncrono pode ser usado para transmitir os dados, e um 'for each' assíncrono pode processar cada cotação, atualizando a interface do usuário com os preços mais recentes. Isso pode ser usado para exibir as cotações atuais de empresas como:
- Tencent (China): Buscando dados de ações de uma grande empresa internacional de tecnologia
- Tata Consultancy Services (Índia): Exibindo atualizações de ações de uma empresa líder em serviços de TI
- Samsung Electronics (Coreia do Sul): Mostrando cotações de ações de uma fabricante global de eletrônicos
- Toyota Motor Corporation (Japão): Monitorando os preços das ações de uma montadora internacional
Conclusão
Iteradores assíncronos e o auxiliar 'for each' assíncrono fornecem uma maneira poderosa e elegante de processar fluxos de dados de forma assíncrona em JavaScript. Ao encapsular a lógica de iteração, você pode simplificar seu código, melhorar a legibilidade e aprimorar o desempenho de suas aplicações. Ao tratar erros, controlar a concorrência e permitir o cancelamento, você pode criar pipelines de processamento de dados assíncronos robustos e escaláveis.