Norsk

Utforsk JavaScript Async Iterator Helpers for å revolusjonere strømprosessering. Lær hvordan du effektivt håndterer asynkrone datastrømmer med map, filter, take, drop og mer.

JavaScript Async Iterator Helpers: Kraftig strømprosessering for moderne applikasjoner

I moderne JavaScript-utvikling er håndtering av asynkrone datastrømmer et vanlig krav. Enten du henter data fra et API, behandler store filer eller håndterer sanntidshendelser, er effektiv styring av asynkron data avgjørende. JavaScripts Async Iterator Helpers gir en kraftig og elegant måte å behandle disse strømmene på, og tilbyr en funksjonell og komponerbar tilnærming til datamanipulering.

Hva er Async Iterators og Async Iterables?

Før vi dykker ned i Async Iterator Helpers, la oss forstå de underliggende konseptene: Async Iterators og Async Iterables.

En Async Iterable er et objekt som definerer en måte å asynkront iterere over verdiene sine. Den gjør dette ved å implementere @@asyncIterator-metoden, som returnerer en Async Iterator.

En Async Iterator er et objekt som tilbyr en next()-metode. Denne metoden returnerer et promise som resolver til et objekt med to egenskaper:

Her er et enkelt eksempel:


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

const asyncIterable = generateSequence(5);

(async () => {
  for await (const value of asyncIterable) {
    console.log(value); // Output: 1, 2, 3, 4, 5 (with 500ms delay between each)
  }
})();

I dette eksempelet er generateSequence en asynkron generatorfunksjon som produserer en sekvens av tall asynkront. for await...of-løkken brukes til å konsumere verdiene fra den asynkrone itererbare.

Introduksjon til Async Iterator Helpers

Async Iterator Helpers utvider funksjonaliteten til Async Iterators, og tilbyr et sett med metoder for å transformere, filtrere og manipulere asynkrone datastrømmer. De muliggjør en funksjonell og komponerbar programmeringsstil, noe som gjør det enklere å bygge komplekse databehandlingspipelines.

De sentrale Async Iterator Helpers inkluderer:

La oss utforske hver hjelper med eksempler.

map()

map()-hjelperen transformerer hvert element i den asynkrone itererbare ved hjelp av en gitt funksjon. Den returnerer en ny asynkron itererbar med de transformerte verdiene.


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 (with 100ms delay)
  }
})();

I dette eksempelet dobler map(x => x * 2) hvert tall i sekvensen.

filter()

filter()-hjelperen velger elementer fra den asynkrone itererbare basert på en gitt betingelse (predikatfunksjon). Den returnerer en ny asynkron itererbar som kun inneholder elementene som oppfyller 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 (with 100ms delay)
  }
})();

I dette eksempelet velger filter(x => x % 2 === 0) kun partallene fra sekvensen.

take()

take()-hjelperen returnerer de første N elementene fra den asynkrone itererbare. Den returnerer en ny asynkron itererbar som kun inneholder det spesifiserte antallet 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 (with 100ms delay)
  }
})();

I dette eksempelet velger take(3) de tre første tallene fra sekvensen.

drop()

drop()-hjelperen hopper over de første N elementene fra den asynkrone itererbare og returnerer resten. Den returnerer en ny asynkron itererbar som inneholder de resterende elementene.


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 (with 100ms delay)
  }
})();

I dette eksempelet hopper drop(2) over de to første tallene fra sekvensen.

toArray()

toArray()-hjelperen konsumerer hele den asynkrone itererbare og samler alle elementene i en array. Den returnerer et promise som resolver til en array som inneholder alle elementene.


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 eksempelet samler toArray() alle tallene fra sekvensen inn i en array.

forEach()

forEach()-hjelperen utfører en gitt funksjon én gang for hvert element i den asynkrone itererbare. Den returnerer *ikke* en ny asynkron itererbar, den utfører funksjonen som en sideeffekt. Dette kan være nyttig for å utføre operasjoner som logging eller oppdatering av et brukergrensesnitt.


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 completed");
})();
// Output: Value: 1, Value: 2, Value: 3, forEach completed

some()

some()-hjelperen tester om minst ett element i den asynkrone itererbare passerer testen implementert av den gitte funksjonen. Den returnerer et promise som resolver til en boolsk verdi (true hvis minst ett element oppfyller 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("Has even number:", hasEvenNumber); // Output: Has even number: true
})();

every()

every()-hjelperen tester om alle elementene i den asynkrone itererbare passerer testen implementert av den gitte funksjonen. Den returnerer et promise som resolver til en boolsk verdi (true hvis alle elementer oppfyller 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("Are all even:", areAllEven); // Output: Are all even: true
})();

find()

find()-hjelperen returnerer det første elementet i den asynkrone itererbare som oppfyller den gitte testfunksjonen. Hvis ingen verdier oppfyller testfunksjonen, returneres undefined. Den returnerer et promise som resolver til det funne elementet 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("First even number:", firstEven); // Output: First even number: 2
})();

reduce()

reduce()-hjelperen utfører en brukerlevert "reducer"-tilbakekallingsfunksjon på hvert element i den asynkrone itererbare, i rekkefølge, og sender returverdien fra beregningen på det foregående elementet videre. Det endelige resultatet av å kjøre redusereren over alle elementene er en enkelt verdi. Den returnerer et promise som resolver til den endelige akkumulerte verdien.


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 bruksområder

Async Iterator Helpers er verdifulle i en rekke scenarioer. La oss utforske noen praktiske eksempler:

1. Behandle data fra et strømmende API

Forestill deg at du bygger et sanntids datavisualiserings-dashboard som mottar data fra et strømmende API. API-et sender kontinuerlig oppdateringer, og du må behandle disse oppdateringene for å vise den nyeste informasjonen.


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);
      // Assuming the API sends JSON objects separated by newlines
      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'; // Replace with your API URL
const dataStream = fetchDataFromAPI(apiURL);

// Process the data stream
(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);
    // Update the dashboard with the processed data
  }
})();

I dette eksempelet henter fetchDataFromAPI data fra et strømmende API, parser JSON-objektene og yielder dem som en asynkron itererbar. filter-hjelperen velger kun metrikkene, og map-hjelperen transformerer dataene til ønsket format før dashboardet oppdateres.

2. Lese og behandle store filer

Anta at du må behandle en stor CSV-fil som inneholder kundedata. I stedet for å laste hele filen inn i minnet, kan du bruke Async Iterator Helpers til å behandle den bit for bit.


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'; // Replace with your file path
const lines = readLinesFromFile(filePath);

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

I dette eksempelet leser readLinesFromFile filen linje for linje og yielder hver linje som en asynkron itererbar. drop(1)-hjelperen hopper over overskriftsraden, map-hjelperen deler linjen inn i kolonner, og filter-hjelperen velger kun kunder fra USA.

3. Håndtere sanntidshendelser

Async Iterator Helpers kan også brukes til å håndtere sanntidshendelser fra kilder som WebSockets. Du kan lage en asynkron itererbar som sender ut hendelser etter hvert som de ankommer, og deretter bruke hjelperne til å behandle disse hendelsene.


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 with null when connection closes
        }
      });

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

const websocketURL = 'wss://example.com/events'; // Replace with your WebSocket URL
const eventStream = createWebSocketStream(websocketURL);

// Process the event stream
(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);
    // Process user login event
  }
})();

I dette eksempelet lager createWebSocketStream en asynkron itererbar som sender ut hendelser mottatt fra en WebSocket. filter-hjelperen velger kun brukerinnloggingshendelser, og map-hjelperen transformerer dataene til ønsket format.

Fordeler med å bruke Async Iterator Helpers

Støtte i nettlesere og kjøretidsmiljøer

Async Iterator Helpers er fortsatt en relativt ny funksjon i JavaScript. Mot slutten av 2024 er de på trinn 3 i TC39-standardiseringsprosessen, noe som betyr at de sannsynligvis vil bli standardisert i nær fremtid. De er imidlertid ennå ikke innebygd støttet i alle nettlesere og Node.js-versjoner.

Nettleserstøtte: Moderne nettlesere som Chrome, Firefox, Safari og Edge legger gradvis til støtte for Async Iterator Helpers. Du kan sjekke den nyeste informasjonen om nettleserkompatibilitet på nettsteder som Can I use... for å se hvilke nettlesere som støtter denne funksjonen.

Node.js-støtte: Nylige versjoner av Node.js (v18 og nyere) gir eksperimentell støtte for Async Iterator Helpers. For å bruke dem, må du kanskje kjøre Node.js med --experimental-async-iterator-flagget.

Polyfills: Hvis du trenger å bruke Async Iterator Helpers i miljøer som ikke støtter dem innebygd, kan du bruke en polyfill. En polyfill er en kodebit som gir den manglende funksjonaliteten. Flere polyfill-biblioteker er tilgjengelige for Async Iterator Helpers; et populært alternativ er core-js-biblioteket.

Implementere egne asynkrone iteratorer

Selv om Async Iterator Helpers gir en praktisk måte å behandle eksisterende asynkrone itererbare, kan du noen ganger trenge å lage dine egne asynkrone iteratorer. Dette lar deg håndtere data fra forskjellige kilder, som databaser, API-er eller filsystemer, på en strømmende måte.

For å lage en egen asynkron iterator, må du implementere @@asyncIterator-metoden på et objekt. Denne metoden skal returnere et objekt med en next()-metode. next()-metoden skal returnere et promise som resolver til et objekt med value- og done-egenskaper.

Her er et eksempel på en egen asynkron iterator som henter data fra et paginert 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'; // Replace with your API URL
const paginatedData = fetchPaginatedData(apiBaseURL);

// Process the paginated data
(async () => {
  for await (const item of paginatedData) {
    console.log('Item:', item);
    // Process the item
  }
})();

I dette eksempelet henter fetchPaginatedData data fra et paginert API, og yielder hvert element etter hvert som det hentes. Den asynkrone iteratoren håndterer pagineringslogikken, noe som gjør det enkelt å konsumere dataene på en strømmende måte.

Potensielle utfordringer og hensyn

Selv om Async Iterator Helpers tilbyr mange fordeler, er det viktig å være klar over noen potensielle utfordringer og hensyn:

Beste praksis for bruk av Async Iterator Helpers

For å få mest mulig ut av Async Iterator Helpers, bør du vurdere følgende beste praksis:

Avanserte teknikker

Komponere egne hjelpere

Du kan lage dine egne asynkrone iterator-hjelpere ved å komponere eksisterende hjelpere eller bygge nye fra bunnen av. Dette lar deg skreddersy funksjonaliteten til dine spesifikke behov og lage gjenbrukbare komponenter.


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

// Example Usage:
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);
  }
})();

Kombinere flere asynkrone itererbare

Du kan kombinere flere asynkrone itererbare til en enkelt asynkron itererbar ved hjelp av teknikker som zip eller merge. Dette lar deg behandle data fra flere kilder samtidig.


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];
    }
}

// Example Usage:
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);
    }
})();

Konklusjon

JavaScript Async Iterator Helpers gir en kraftig og elegant måte å behandle asynkrone datastrømmer på. De tilbyr en funksjonell og komponerbar tilnærming til datamanipulering, noe som gjør det enklere å bygge komplekse databehandlingspipelines. Ved å forstå de sentrale konseptene til Async Iterators og Async Iterables og mestre de ulike hjelpemetodene, kan du betydelig forbedre effektiviteten og vedlikeholdbarheten til din asynkrone JavaScript-kode. Ettersom støtten i nettlesere og kjøretidsmiljøer fortsetter å vokse, er Async Iterator Helpers posisjonert til å bli et essensielt verktøy for moderne JavaScript-utviklere.