Mestr asynkron iteration i JavaScript med 'for await...of'-løkken og brugerdefinerede async iterator-hjælpere. Forbedr stream-behandling og datahåndtering med praktiske eksempler.
JavaScript Async Iterator Hjælper: For Each - Iteration for Stream-behandling
Asynkron programmering er en hjørnesten i moderne JavaScript-udvikling, som gør det muligt for applikationer at håndtere tidskrævende operationer uden at blokere hovedtråden. Async iterators, introduceret i ECMAScript 2018, giver en kraftfuld mekanisme til at behandle datastrømme asynkront. Dette blogindlæg dykker ned i konceptet med async iterators og demonstrerer, hvordan man implementerer en asynkron 'for each' hjælpefunktion for at strømline stream-behandling.
Forståelse af Async Iterators
En async iterator er et objekt, der overholder AsyncIterator-interfacet. Den definerer en next()-metode, der returnerer et promise, som resolver til et objekt med to egenskaber:
value: Den næste værdi i sekvensen.done: En boolean, der angiver, om iteratoren er afsluttet.
Async iterators bruges almindeligvis til at forbruge data fra asynkrone kilder som netværksstrømme, filsystemer eller databaser. for await...of-løkken giver en bekvem syntaks til at iterere over async iterables.
Eksempel: Asynkron læsning fra en fil
Overvej et scenarie, hvor du skal læse en stor fil linje for linje uden at blokere hovedtråden. Du kan opnå dette ved hjælp af en 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}`);
}
}
// Eksempel på brug
processFile('path/to/your/file.txt');
I dette eksempel er readFileLines en async generator-funktion, der yielder hver linje i filen, efterhånden som den læses. processFile-funktionen itererer derefter over linjerne ved hjælp af for await...of og behandler hver linje asynkront.
Implementering af en Async 'For Each' Hjælper
Selvom for await...of-løkken er nyttig, kan den blive omstændelig, når man skal udføre komplekse operationer på hvert element i strømmen. En async 'for each' hjælpefunktion kan forenkle denne proces ved at indkapsle iterationslogikken.
Grundlæggende Implementering
Her er en grundlæggende implementering af en async 'for each'-funktion:
async function asyncForEach(iterable, callback) {
for await (const item of iterable) {
await callback(item);
}
}
Denne funktion tager en async iterable og en callback-funktion som argumenter. Den itererer over den iterable ved hjælp af for await...of og kalder callback-funktionen for hvert element. Callback-funktionen bør også være asynkron, hvis du vil afvente dens fuldførelse, før du går videre til næste element.
Eksempel: Behandling af data fra et API
Antag, at du henter data fra et API, der returnerer en strøm af elementer. Du kan bruge async 'for each'-hjælperen til at behandle hvert element, efterhånden som det ankommer:
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;
}
// Antager at API'et returnerer JSON-chunks
const chunk = decoder.decode(value);
const items = JSON.parse(`[${chunk.replace(/}\{/g, '},{')}]`); // Opdel chunks i et gyldigt JSON-array
for(const item of items){
yield item;
}
}
} finally {
reader.releaseLock();
}
}
async function processItem(item) {
// Simuler en asynkron operation
await new Promise(resolve => setTimeout(resolve, 100));
console.log(`Processing item: ${JSON.stringify(item)}`);
}
async function main() {
const apiUrl = 'https://api.example.com/data'; // Erstat med dit API-endepunkt
await asyncForEach(fetchDataStream(apiUrl), processItem);
console.log('Finished processing data.');
}
// Eksempel på brug
main();
I dette eksempel henter fetchDataStream data fra API'et og yielder hvert element, efterhånden som det modtages. processItem-funktionen simulerer en asynkron operation på hvert element. asyncForEach-hjælperen forenkler derefter iterations- og behandlingslogikken.
Forbedringer og Overvejelser
Fejlhåndtering
Det er afgørende at håndtere fejl, der kan opstå under asynkron iteration. Du kan pakke callback-funktionen ind i en try...catch-blok for at fange og håndtere undtagelser:
async function asyncForEach(iterable, callback) {
for await (const item of iterable) {
try {
await callback(item);
} catch (error) {
console.error(`Error processing item: ${item}`, error);
// Du kan vælge at genkaste fejlen eller fortsætte behandlingen
}
}
}
Samtidighedskontrol
Som standard behandler async 'for each'-hjælperen elementer sekventielt. Hvis du har brug for at behandle elementer samtidigt, kan du bruge en Promise-pulje til at begrænse antallet af samtidige operationer:
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) {
// Simuler en asynkron operation
await new Promise(resolve => setTimeout(resolve, 100));
console.log(`Processing item: ${JSON.stringify(item)}`);
}
async function main() {
const apiUrl = 'https://api.example.com/data'; // Erstat med dit API-endepunkt
await asyncForEachConcurrent(fetchDataStream(apiUrl), processItem, 5); // Samtidighed på 5
console.log('Finished processing data.');
}
I dette eksempel begrænser asyncForEachConcurrent antallet af samtidige callback-kørsler til det specificerede samtidighedsniveau. Dette kan forbedre ydeevnen, når man arbejder med store datastrømme.
Annullering
I nogle tilfælde kan du have brug for at annullere iterationsprocessen før tid. Du kan opnå dette ved at bruge en 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(); // Annuller efter 2 sekunder
}, 2000);
const apiUrl = 'https://api.example.com/data'; // Erstat med dit API-endepunkt
await asyncForEach(fetchDataStream(apiUrl), processItem, signal);
console.log('Finished processing data.');
}
I dette eksempel tjekker asyncForEach-funktionen signal.aborted-egenskaben før hver iteration. Hvis signalet er annulleret, stoppes iterationen.
Anvendelser i den Virkelige Verden
Async iterators og async 'for each'-hjælperen kan anvendes i en bred vifte af virkelige scenarier:
- Databehandlings-pipelines: Behandling af store datasæt fra databaser eller filsystemer.
- Realtids-datastrømme: Håndtering af data fra web sockets, meddelelseskøer eller sensornetværk.
- API-forbrug: Hentning og behandling af data fra API'er, der returnerer strømme af elementer.
- Billed- og videobehandling: Behandling af store mediefiler i bidder.
- Loganalyse: Analyse af store logfiler linje for linje.
Eksempel - Internationale Aktiedata: Overvej en applikation, der henter aktiekurser i realtid fra forskellige internationale børser. En async iterator kan bruges til at streame dataene, og en async 'for each' kan behandle hvert kurs, opdatere brugergrænsefladen med de seneste priser. Dette kan bruges til at vise aktuelle aktiekurser for virksomheder som:
- Tencent (Kina): Hentning af aktiedata for en stor international teknologivirksomhed
- Tata Consultancy Services (Indien): Visning af aktieopdateringer fra en førende IT-servicevirksomhed
- Samsung Electronics (Sydkorea): Fremvisning af aktiekurser fra en global elektronikproducent
- Toyota Motor Corporation (Japan): Overvågning af aktiekurser for en international bilproducent
Konklusion
Async iterators og async 'for each'-hjælperen giver en kraftfuld og elegant måde at behandle datastrømme asynkront i JavaScript. Ved at indkapsle iterationslogikken kan du forenkle din kode, forbedre læsbarheden og øge ydeevnen i dine applikationer. Ved at håndtere fejl, kontrollere samtidighed og muliggøre annullering kan du skabe robuste og skalerbare asynkrone databehandlings-pipelines.