Beheers asynchrone iteratie in JavaScript met de 'for await...of'-lus en aangepaste async iterator helpers. Verbeter streamverwerking en datahantering met praktische voorbeelden.
JavaScript Async Iterator Helper: For Each - Iteratie bij Streamverwerking
Asynchroon programmeren is een hoeksteen van moderne JavaScript-ontwikkeling, waardoor applicaties tijdrovende operaties kunnen afhandelen zonder de hoofdthread te blokkeren. Async iterators, geïntroduceerd in ECMAScript 2018, bieden een krachtig mechanisme voor het asynchroon verwerken van datastromen. Deze blogpost duikt in het concept van async iterators en demonstreert hoe je een asynchrone 'for each' helperfunctie kunt implementeren om streamverwerking te stroomlijnen.
Async Iterators Begrijpen
Een async iterator is een object dat voldoet aan de AsyncIterator-interface. Het definieert een next()-methode die een promise retourneert, welke resulteert in een object met twee eigenschappen:
value: De volgende waarde in de reeks.done: Een boolean die aangeeft of de iterator voltooid is.
Async iterators worden vaak gebruikt om data te consumeren van asynchrone bronnen zoals netwerkstreams, bestandssystemen of databases. De for await...of-lus biedt een handige syntaxis voor het itereren over async iterables.
Voorbeeld: Asynchroon Lezen van een Bestand
Stel je een scenario voor waarin je een groot bestand regel voor regel moet lezen zonder de hoofdthread te blokkeren. Dit kun je bereiken met een async iterator:
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}`);
}
}
// Voorbeeldgebruik
processFile('path/to/your/file.txt');
In dit voorbeeld is readFileLines een async generator-functie die elke regel van het bestand 'yieldt' zodra deze wordt gelezen. De processFile-functie itereert vervolgens over de regels met for await...of, waarbij elke regel asynchroon wordt verwerkt.
Een Async 'For Each' Helper Implementeren
Hoewel de for await...of-lus nuttig is, kan deze omslachtig worden wanneer je complexe operaties moet uitvoeren op elk element in de stream. Een asynchrone 'for each' helperfunctie kan dit proces vereenvoudigen door de iteratielogica in te kapselen.
Basisimplementatie
Hier is een basisimplementatie van een asynchrone 'for each'-functie:
async function asyncForEach(iterable, callback) {
for await (const item of iterable) {
await callback(item);
}
}
Deze functie accepteert een async iterable en een callback-functie als argumenten. Het itereert over de iterable met for await...of en roept de callback-functie aan voor elk item. De callback-functie moet ook asynchroon zijn als je wilt wachten op de voltooiing ervan voordat je naar het volgende item gaat.
Voorbeeld: Data Verwerken van een API
Stel dat je data ophaalt van een API die een stroom van items retourneert. Je kunt de async 'for each' helper gebruiken om elk item te verwerken zodra het binnenkomt:
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;
}
// Aangenomen dat de API JSON-chunks retourneert
const chunk = decoder.decode(value);
const items = JSON.parse(`[${chunk.replace(/\}\{/g, '},{')}]`); //Chunks splitsen in een geldige JSON-array
for(const item of items){
yield item;
}
}
} finally {
reader.releaseLock();
}
}
async function processItem(item) {
// Simuleer een asynchrone operatie
await new Promise(resolve => setTimeout(resolve, 100));
console.log(`Processing item: ${JSON.stringify(item)}`);
}
async function main() {
const apiUrl = 'https://api.example.com/data'; // Vervang door uw API-eindpunt
await asyncForEach(fetchDataStream(apiUrl), processItem);
console.log('Finished processing data.');
}
// Voorbeeldgebruik
main();
In dit voorbeeld haalt fetchDataStream data op van de API en 'yieldt' elk item zodra het wordt ontvangen. De processItem-functie simuleert een asynchrone operatie op elk item. De asyncForEach helper vereenvoudigt vervolgens de iteratie- en verwerkingslogica.
Verbeteringen en Overwegingen
Foutafhandeling
Het is cruciaal om fouten af te handelen die kunnen optreden tijdens asynchrone iteratie. Je kunt de callback-functie in een try...catch-blok wikkelen om uitzonderingen op te vangen en af te handelen:
async function asyncForEach(iterable, callback) {
for await (const item of iterable) {
try {
await callback(item);
} catch (error) {
console.error(`Error processing item: ${item}`, error);
// Je kunt ervoor kiezen de fout opnieuw te werpen of door te gaan met verwerken
}
}
}
Concurrencybeheer
Standaard verwerkt de async 'for each' helper items sequentieel. Als je items gelijktijdig (concurrent) wilt verwerken, kun je een Promise-pool gebruiken om het aantal gelijktijdige operaties te beperken:
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) {
// Simuleer een asynchrone operatie
await new Promise(resolve => setTimeout(resolve, 100));
console.log(`Processing item: ${JSON.stringify(item)}`);
}
async function main() {
const apiUrl = 'https://api.example.com/data'; // Vervang door uw API-eindpunt
await asyncForEachConcurrent(fetchDataStream(apiUrl), processItem, 5); // Concurrency van 5
console.log('Finished processing data.');
}
In dit voorbeeld beperkt asyncForEachConcurrent het aantal gelijktijdige callback-uitvoeringen tot het opgegeven concurrency-niveau. Dit kan de prestaties verbeteren bij het omgaan met grote datastromen.
Annulering
In sommige gevallen moet je het iteratieproces mogelijk voortijdig annuleren. Dit kun je bereiken met een 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(); // Annuleer na 2 seconden
}, 2000);
const apiUrl = 'https://api.example.com/data'; // Vervang door uw API-eindpunt
await asyncForEach(fetchDataStream(apiUrl), processItem, signal);
console.log('Finished processing data.');
}
In dit voorbeeld controleert de asyncForEach-functie de eigenschap signal.aborted vóór elke iteratie. Als het signaal is geannuleerd, wordt de iteratie gestopt.
Toepassingen in de Praktijk
Async iterators en de async 'for each' helper kunnen worden toegepast op een breed scala van praktijkscenario's:
- Dataverwerkingspipelines: Grote datasets verwerken uit databases of bestandssystemen.
- Real-time datastromen: Data verwerken van websockets, message queues of sensornetwerken.
- API-consumptie: Data ophalen en verwerken van API's die stromen van items retourneren.
- Beeld- en videoverwerking: Grote mediabestanden in chunks verwerken.
- Log-analyse: Grote logbestanden regel voor regel analyseren.
Voorbeeld - Internationale Aandelendata: Denk aan een applicatie die real-time aandelenkoersen ophaalt van verschillende internationale beurzen. Een async iterator kan worden gebruikt om de data te streamen, en een async 'for each' kan elke koers verwerken, waarbij de gebruikersinterface wordt bijgewerkt met de laatste prijzen. Dit kan worden gebruikt om de huidige aandelenkoersen van bedrijven zoals:
- Tencent (China): Aandelendata ophalen van een groot internationaal technologiebedrijf
- Tata Consultancy Services (India): Aandelenupdates weergeven van een toonaangevend IT-dienstverleningsbedrijf
- Samsung Electronics (Zuid-Korea): Aandelenkoersen tonen van een wereldwijde elektronicafabrikant
- Toyota Motor Corporation (Japan): Aandelenprijzen monitoren van een internationale autofabrikant
Conclusie
Async iterators en de async 'for each' helper bieden een krachtige en elegante manier om datastromen asynchroon te verwerken in JavaScript. Door de iteratielogica in te kapselen, kun je je code vereenvoudigen, de leesbaarheid verbeteren en de prestaties van je applicaties verhogen. Door fouten af te handelen, concurrency te beheren en annulering mogelijk te maken, kun je robuuste en schaalbare asynchrone dataverwerkingspipelines creëren.