Dansk

Udforsk JavaScript Async Iterator Helpers for at revolutionere stream-behandling. Lær at håndtere asynkrone datastrømme effektivt med map, filter, take, drop og mere.

JavaScript Async Iterator Helpers: Kraftfuld Stream-behandling til Moderne Applikationer

I moderne JavaScript-udvikling er håndtering af asynkrone datastrømme et almindeligt krav. Uanset om du henter data fra en API, behandler store filer eller håndterer realtidshændelser, er det afgørende at administrere asynkrone data effektivt. JavaScripts Async Iterator Helpers giver en kraftfuld og elegant måde at behandle disse strømme på, og tilbyder en funktionel og komponerbar tilgang til datamanipulation.

Hvad er Async Iterators og Async Iterables?

Før vi dykker ned i Async Iterator Helpers, lad os forstå de grundlæggende koncepter: Async Iterators og Async Iterables.

En Async Iterable er et objekt, der definerer en måde at iterere asynkront over sine værdier. Den gør dette ved at implementere @@asyncIterator-metoden, som returnerer en Async Iterator.

En Async Iterator er et objekt, der har en next()-metode. Denne metode returnerer et promise, der resolver til et objekt med to egenskaber:

Her er et simpelt eksempel:


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 500)); // Simuler en asynkron operation
    yield i;
  }
}

const asyncIterable = generateSequence(5);

(async () => {
  for await (const value of asyncIterable) {
    console.log(value); // Output: 1, 2, 3, 4, 5 (med 500ms forsinkelse mellem hver)
  }
})();

I dette eksempel er generateSequence en asynkron generatorfunktion, der producerer en sekvens af tal asynkront. for await...of-løkken bruges til at forbruge værdierne fra den asynkrone iterable.

Introduktion til Async Iterator Helpers

Async Iterator Helpers udvider funktionaliteten af Async Iterators og giver et sæt metoder til at transformere, filtrere og manipulere asynkrone datastrømme. De muliggør en funktionel og komponerbar programmeringsstil, hvilket gør det lettere at bygge komplekse databehandlings-pipelines.

Kernen af Async Iterator Helpers inkluderer:

Lad os udforske hver helper med eksempler.

map()

map()-helperen transformerer hvert element i den asynkrone iterable ved hjælp af en given funktion. Den returnerer en ny asynkron iterable med de transformerede værdier.


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(5);

const doubledIterable = asyncIterable.map(x => x * 2);

(async () => {
  for await (const value of doubledIterable) {
    console.log(value); // Output: 2, 4, 6, 8, 10 (med 100ms forsinkelse)
  }
})();

I dette eksempel fordobler map(x => x * 2) hvert tal i sekvensen.

filter()

filter()-helperen vælger elementer fra den asynkrone iterable baseret på en given betingelse (prædikatfunktion). Den returnerer en ny asynkron iterable, der kun indeholder de elementer, der opfylder betingelsen.


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(10);

const evenNumbersIterable = asyncIterable.filter(x => x % 2 === 0);

(async () => {
  for await (const value of evenNumbersIterable) {
    console.log(value); // Output: 2, 4, 6, 8, 10 (med 100ms forsinkelse)
  }
})();

I dette eksempel vælger filter(x => x % 2 === 0) kun de lige tal fra sekvensen.

take()

take()-helperen returnerer de første N elementer fra den asynkrone iterable. Den returnerer en ny asynkron iterable, der kun indeholder det specificerede antal elementer.


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(5);

const firstThreeIterable = asyncIterable.take(3);

(async () => {
  for await (const value of firstThreeIterable) {
    console.log(value); // Output: 1, 2, 3 (med 100ms forsinkelse)
  }
})();

I dette eksempel vælger take(3) de første tre tal fra sekvensen.

drop()

drop()-helperen springer de første N elementer fra den asynkrone iterable over og returnerer resten. Den returnerer en ny asynkron iterable, der indeholder de resterende elementer.


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(5);

const afterFirstTwoIterable = asyncIterable.drop(2);

(async () => {
  for await (const value of afterFirstTwoIterable) {
    console.log(value); // Output: 3, 4, 5 (med 100ms forsinkelse)
  }
})();

I dette eksempel springer drop(2) de første to tal fra sekvensen over.

toArray()

toArray()-helperen gennemløber hele den asynkrone iterable og samler alle elementer i et array. Den returnerer et promise, der resolver til et array, som indeholder alle elementerne.


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(5);

(async () => {
  const numbersArray = await asyncIterable.toArray();
  console.log(numbersArray); // Output: [1, 2, 3, 4, 5]
})();

I dette eksempel samler toArray() alle tallene fra sekvensen i et array.

forEach()

forEach()-helperen udfører en given funktion én gang for hvert element i den asynkrone iterable. Den returnerer *ikke* en ny asynkron iterable, den udfører funktionen for dens sideeffekter. Dette kan være nyttigt til at udføre handlinger som logning eller opdatering af en brugergrænseflade.


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(3);

(async () => {
  await asyncIterable.forEach(value => {
    console.log("Value:", value);
  });
  console.log("forEach fuldført");
})();
// Output: Value: 1, Value: 2, Value: 3, forEach fuldført

some()

some()-helperen tester, om mindst ét element i den asynkrone iterable består den test, der er implementeret af den givne funktion. Den returnerer et promise, der resolver til en boolean-værdi (true hvis mindst ét element opfylder betingelsen, ellers false).


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(5);

(async () => {
  const hasEvenNumber = await asyncIterable.some(x => x % 2 === 0);
  console.log("Har lige tal:", hasEvenNumber); // Output: Har lige tal: true
})();

every()

every()-helperen tester, om alle elementer i den asynkrone iterable består den test, der er implementeret af den givne funktion. Den returnerer et promise, der resolver til en boolean-værdi (true hvis alle elementer opfylder betingelsen, ellers false).


async function* generateSequence(end) {
  for (let i = 2; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(4);

(async () => {
  const areAllEven = await asyncIterable.every(x => x % 2 === 0);
  console.log("Er alle lige:", areAllEven); // Output: Er alle lige: true
})();

find()

find()-helperen returnerer det første element i den asynkrone iterable, der opfylder den givne testfunktion. Hvis ingen værdier opfylder testfunktionen, returneres undefined. Den returnerer et promise, der resolver til det fundne element eller undefined.


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(5);

(async () => {
  const firstEven = await asyncIterable.find(x => x % 2 === 0);
  console.log("Første lige tal:", firstEven); // Output: Første lige tal: 2
})();

reduce()

reduce()-helperen udfører en brugerdefineret "reducer" callback-funktion på hvert element i den asynkrone iterable, i rækkefølge, og sender returværdien fra beregningen på det foregående element videre. Det endelige resultat af at køre reduceren over alle elementer er en enkelt værdi. Den returnerer et promise, der resolver til den endelige akkumulerede værdi.


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(5);

(async () => {
  const sum = await asyncIterable.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
  console.log("Sum:", sum); // Output: Sum: 15
})();

Praktiske Eksempler og Anvendelsestilfælde

Async Iterator Helpers er værdifulde i en række forskellige scenarier. Lad os udforske nogle praktiske eksempler:

1. Behandling af Data fra en Streaming API

Forestil dig, at du bygger et realtids-datavisualiserings-dashboard, der modtager data fra en streaming API. API'en sender løbende opdateringer, og du skal behandle disse opdateringer for at vise de seneste oplysninger.


async function* fetchDataFromAPI(url) {
  let response = await fetch(url);

  if (!response.body) {
    throw new Error("ReadableStream not supported in this environment");
  }

  const reader = response.body.getReader();
  const decoder = new TextDecoder();

  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) {
        break;
      }
      const chunk = decoder.decode(value);
      // Antager at API'en sender JSON-objekter adskilt af linjeskift
      const lines = chunk.split('\n');
      for (const line of lines) {
        if (line.trim() !== '') {
          yield JSON.parse(line);
        }
      }
    }
  } finally {
    reader.releaseLock();
  }
}

const apiURL = 'https://example.com/streaming-api'; // Erstat med din API URL
const dataStream = fetchDataFromAPI(apiURL);

// Behandl datastrømmen
(async () => {
  for await (const data of dataStream.filter(item => item.type === 'metric').map(item => ({ timestamp: item.timestamp, value: item.value }))) {
    console.log('Processed Data:', data);
    // Opdater dashboardet med de behandlede data
  }
})();

I dette eksempel henter fetchDataFromAPI data fra en streaming API, parser JSON-objekterne og yielder dem som en asynkron iterable. filter-helperen vælger kun metrics, og map-helperen transformerer dataene til det ønskede format, før dashboardet opdateres.

2. Læsning og Behandling af Store Filer

Antag, at du skal behandle en stor CSV-fil, der indeholder kundedata. I stedet for at indlæse hele filen i hukommelsen, kan du bruge Async Iterator Helpers til at behandle den bid for bid.


async function* readLinesFromFile(filePath) {
  const file = await fsPromises.open(filePath, 'r');

  try {
    let buffer = Buffer.alloc(1024);
    let fileOffset = 0;
    let remainder = '';

    while (true) {
      const { bytesRead } = await file.read(buffer, 0, buffer.length, fileOffset);
      if (bytesRead === 0) {
        if (remainder) {
          yield remainder;
        }
        break;
      }

      fileOffset += bytesRead;
      const chunk = buffer.toString('utf8', 0, bytesRead);
      const lines = chunk.split('\n');

      lines[0] = remainder + lines[0];
      remainder = lines.pop() || '';

      for (const line of lines) {
        yield line;
      }
    }
  } finally {
    await file.close();
  }
}

const filePath = './customer_data.csv'; // Erstat med din filsti
const lines = readLinesFromFile(filePath);

// Behandl linjerne
(async () => {
  for await (const customerData of lines.drop(1).map(line => line.split(',')).filter(data => data[2] === 'USA')) {
    console.log('Customer from USA:', customerData);
    // Behandl kundedata fra USA
  }
})();

I dette eksempel læser readLinesFromFile filen linje for linje og yielder hver linje som en asynkron iterable. drop(1)-helperen springer overskriftsrækken over, map-helperen splitter linjen op i kolonner, og filter-helperen vælger kun kunder fra USA.

3. Håndtering af Realtidshændelser

Async Iterator Helpers kan også bruges til at håndtere realtidshændelser fra kilder som WebSockets. Du kan oprette en asynkron iterable, der udsender hændelser, efterhånden som de ankommer, og derefter bruge helperne til at behandle disse hændelser.


async function* createWebSocketStream(url) {
  const ws = new WebSocket(url);

  yield new Promise((resolve, reject) => {
      ws.onopen = () => {
          resolve();
      };
      ws.onerror = (error) => {
          reject(error);
      };
  });

  try {
    while (ws.readyState === WebSocket.OPEN) {
      yield new Promise((resolve, reject) => {
        ws.onmessage = (event) => {
          resolve(JSON.parse(event.data));
        };
        ws.onerror = (error) => {
          reject(error);
        };
        ws.onclose = () => {
           resolve(null); // Resolve med null, når forbindelsen lukkes
        }
      });

    }
  } finally {
    ws.close();
  }
}

const websocketURL = 'wss://example.com/events'; // Erstat med din WebSocket URL
const eventStream = createWebSocketStream(websocketURL);

// Behandl hændelsesstrømmen
(async () => {
  for await (const event of eventStream.filter(event => event.type === 'user_login').map(event => ({ userId: event.userId, timestamp: event.timestamp }))) {
    console.log('User Login Event:', event);
    // Behandl bruger-login-hændelse
  }
})();

I dette eksempel opretter createWebSocketStream en asynkron iterable, der udsender hændelser modtaget fra en WebSocket. filter-helperen vælger kun bruger-login-hændelser, og map-helperen transformerer dataene til det ønskede format.

Fordele ved at Bruge Async Iterator Helpers

Browser- og Runtime-understøttelse

Async Iterator Helpers er stadig en relativt ny funktion i JavaScript. I slutningen af 2024 er de i Trin 3 af TC39-standardiseringsprocessen, hvilket betyder, at de sandsynligvis vil blive standardiseret i den nærmeste fremtid. De er dog endnu ikke understøttet native i alle browsere og Node.js-versioner.

Browserunderstøttelse: Moderne browsere som Chrome, Firefox, Safari og Edge tilføjer gradvist understøttelse for Async Iterator Helpers. Du kan tjekke de seneste oplysninger om browserkompatibilitet på websteder som Can I use... for at se, hvilke browsere der understøtter denne funktion.

Node.js-understøttelse: Nyere versioner af Node.js (v18 og derover) giver eksperimentel understøttelse for Async Iterator Helpers. For at bruge dem skal du muligvis køre Node.js med --experimental-async-iterator-flaget.

Polyfills: Hvis du har brug for at bruge Async Iterator Helpers i miljøer, der ikke understøtter dem native, kan du bruge en polyfill. En polyfill er et stykke kode, der tilvejebringer den manglende funktionalitet. Der findes flere polyfill-biblioteker til Async Iterator Helpers; en populær mulighed er core-js-biblioteket.

Implementering af Brugerdefinerede Async Iterators

Selvom Async Iterator Helpers giver en bekvem måde at behandle eksisterende asynkrone iterables på, kan du nogle gange have brug for at oprette dine egne brugerdefinerede asynkrone iteratorer. Dette giver dig mulighed for at håndtere data fra forskellige kilder, såsom databaser, API'er eller filsystemer, på en streaming-måde.

For at oprette en brugerdefineret asynkron iterator skal du implementere @@asyncIterator-metoden på et objekt. Denne metode skal returnere et objekt med en next()-metode. next()-metoden skal returnere et promise, der resolver til et objekt med value- og done-egenskaber.

Her er et eksempel på en brugerdefineret asynkron iterator, der henter data fra en pagineret API:


async function* fetchPaginatedData(baseURL) {
  let page = 1;
  let hasMore = true;

  while (hasMore) {
    const url = `${baseURL}?page=${page}`;
    const response = await fetch(url);
    const data = await response.json();

    if (data.results.length === 0) {
      hasMore = false;
      break;
    }

    for (const item of data.results) {
      yield item;
    }

    page++;
  }
}

const apiBaseURL = 'https://api.example.com/data'; // Erstat med din API URL
const paginatedData = fetchPaginatedData(apiBaseURL);

// Behandl de paginerede data
(async () => {
  for await (const item of paginatedData) {
    console.log('Item:', item);
    // Behandl elementet
  }
})();

I dette eksempel henter fetchPaginatedData data fra en pagineret API og yielder hvert element, efterhånden som det hentes. Den asynkrone iterator håndterer pagineringslogikken, hvilket gør det nemt at forbruge dataene på en streaming-måde.

Potentielle Udfordringer og Overvejelser

Selvom Async Iterator Helpers tilbyder talrige fordele, er det vigtigt at være opmærksom på nogle potentielle udfordringer og overvejelser:

Bedste Praksis for Brug af Async Iterator Helpers

For at få mest muligt ud af Async Iterator Helpers, bør du overveje følgende bedste praksis:

Avancerede Teknikker

Sammensætning af Brugerdefinerede Helpers

Du kan oprette dine egne brugerdefinerede async iterator helpers ved at sammensætte eksisterende helpers eller bygge nye fra bunden. Dette giver dig mulighed for at skræddersy funktionaliteten til dine specifikke behov og skabe genanvendelige komponenter.


async function* takeWhile(asyncIterable, predicate) {
  for await (const value of asyncIterable) {
    if (!predicate(value)) {
      break;
    }
    yield value;
  }
}

// Eksempel på brug:
async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(10);
const firstFive = takeWhile(asyncIterable, x => x <= 5);

(async () => {
  for await (const value of firstFive) {
    console.log(value);
  }
})();

Kombinering af Flere Async Iterables

Du kan kombinere flere asynkrone iterables til en enkelt asynkron iterable ved hjælp af teknikker som zip eller merge. Dette giver dig mulighed for at behandle data fra flere kilder samtidigt.


async function* zip(asyncIterable1, asyncIterable2) {
    const iterator1 = asyncIterable1[Symbol.asyncIterator]();
    const iterator2 = asyncIterable2[Symbol.asyncIterator]();

    while (true) {
        const result1 = await iterator1.next();
        const result2 = await iterator2.next();

        if (result1.done || result2.done) {
            break;
        }

        yield [result1.value, result2.value];
    }
}

// Eksempel på brug:
async function* generateSequence1(end) {
    for (let i = 1; i <= end; i++) {
        yield i;
    }
}

async function* generateSequence2(end) {
    for (let i = 10; i <= end + 9; i++) {
        yield i;
    }
}

const iterable1 = generateSequence1(5);
const iterable2 = generateSequence2(5);

(async () => {
    for await (const [value1, value2] of zip(iterable1, iterable2)) {
        console.log(value1, value2);
    }
})();

Konklusion

JavaScript Async Iterator Helpers giver en kraftfuld og elegant måde at behandle asynkrone datastrømme på. De tilbyder en funktionel og komponerbar tilgang til datamanipulation, hvilket gør det lettere at bygge komplekse databehandlings-pipelines. Ved at forstå de grundlæggende koncepter i Async Iterators og Async Iterables og mestre de forskellige helper-metoder, kan du markant forbedre effektiviteten og vedligeholdelsen af din asynkrone JavaScript-kode. I takt med at understøttelsen i browsere og runtimes fortsætter med at vokse, er Async Iterator Helpers klar til at blive et essentielt værktøj for moderne JavaScript-udviklere.