Suomi

Tutustu JavaScriptin Async Iterator Helper -apuohjelmiin ja mullista tietovirtojen käsittely. Opi käsittelemään asynkronisia tietovirtoja tehokkaasti map-, filter-, take-, drop- ja muiden metodien avulla.

JavaScriptin Async Iterator Helpers: Tehokasta tietovirtojen käsittelyä moderneille sovelluksille

Nykyaikaisessa JavaScript-kehityksessä asynkronisten tietovirtojen käsittely on yleinen vaatimus. Haetpa tietoa API:sta, käsittelet suuria tiedostoja tai hallinnoit reaaliaikaisia tapahtumia, asynkronisen datan tehokas hallinta on ratkaisevan tärkeää. JavaScriptin Async Iterator Helpers -apuohjelmat tarjoavat tehokkaan ja elegantin tavan käsitellä näitä tietovirtoja, tarjoten funktionaalisen ja koostettavan lähestymistavan datan manipulointiin.

Mitä ovat asynkroniset iteraattorit ja asynkroniset iteroitavat?

Ennen kuin syvennymme Async Iterator Helper -apuohjelmiin, on tärkeää ymmärtää niiden taustalla olevat käsitteet: asynkroniset iteraattorit ja asynkroniset iteroitavat.

Asynkroninen iteroitava (Async Iterable) on objekti, joka määrittelee tavan iteroida sen arvojen yli asynkronisesti. Se tekee tämän toteuttamalla @@asyncIterator-metodin, joka palauttaa asynkronisen iteraattorin (Async Iterator).

Asynkroninen iteraattori on objekti, joka tarjoaa next()-metodin. Tämä metodi palauttaa promisen, joka ratkeaa objektiksi, jolla on kaksi ominaisuutta:

Tässä on yksinkertainen esimerkki:


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

const asyncIterable = generateSequence(5);

(async () => {
  for await (const value of asyncIterable) {
    console.log(value); // Tuloste: 1, 2, 3, 4, 5 (500 ms viiveellä kunkin välillä)
  }
})();

Tässä esimerkissä generateSequence on asynkroninen generaattorifunktio, joka tuottaa numerosarjan asynkronisesti. for await...of -silmukkaa käytetään arvojen kuluttamiseen asynkronisesta iteroitavasta.

Esittelyssä Async Iterator Helpers

Async Iterator Helpers -apuohjelmat laajentavat asynkronisten iteraattoreiden toiminnallisuutta tarjoamalla joukon metodeja asynkronisten tietovirtojen muuntamiseen, suodattamiseen ja manipulointiin. Ne mahdollistavat funktionaalisen ja koostettavan ohjelmointityylin, mikä helpottaa monimutkaisten datankäsittelyputkien rakentamista.

Keskeisimpiä Async Iterator Helper -apuohjelmia ovat:

Tutustutaan jokaiseen apuohjelmaan esimerkkien avulla.

map()

map()-apuohjelma muuntaa jokaisen asynkronisen iteroitavan elementin annetulla funktiolla. Se palauttaa uuden asynkronisen iteroitavan, joka sisältää muunnetut arvot.


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); // Tuloste: 2, 4, 6, 8, 10 (100 ms viiveellä)
  }
})();

Tässä esimerkissä map(x => x * 2) kaksinkertaistaa jokaisen numeron sekvenssissä.

filter()

filter()-apuohjelma valitsee elementtejä asynkronisesta iteroitavasta annetun ehdon (predikaattifunktion) perusteella. Se palauttaa uuden asynkronisen iteroitavan, joka sisältää vain ehdon täyttävät elementit.


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); // Tuloste: 2, 4, 6, 8, 10 (100 ms viiveellä)
  }
})();

Tässä esimerkissä filter(x => x % 2 === 0) valitsee sekvenssistä vain parilliset luvut.

take()

take()-apuohjelma palauttaa N ensimmäistä elementtiä asynkronisesta iteroitavasta. Se palauttaa uuden asynkronisen iteroitavan, joka sisältää vain määritetyn määrän elementtejä.


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); // Tuloste: 1, 2, 3 (100 ms viiveellä)
  }
})();

Tässä esimerkissä take(3) valitsee sekvenssistä kolme ensimmäistä numeroa.

drop()

drop()-apuohjelma ohittaa N ensimmäistä elementtiä asynkronisesta iteroitavasta ja palauttaa loput. Se palauttaa uuden asynkronisen iteroitavan, joka sisältää jäljelle jääneet elementit.


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); // Tuloste: 3, 4, 5 (100 ms viiveellä)
  }
})();

Tässä esimerkissä drop(2) ohittaa sekvenssistä kaksi ensimmäistä numeroa.

toArray()

toArray()-apuohjelma kuluttaa koko asynkronisen iteroitavan ja kerää kaikki elementit taulukkoon. Se palauttaa promisen, joka ratkeaa taulukoksi, joka sisältää kaikki elementit.


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); // Tuloste: [1, 2, 3, 4, 5]
})();

Tässä esimerkissä toArray() kerää kaikki numerot sekvenssistä taulukkoon.

forEach()

forEach()-apuohjelma suorittaa annetun funktion kerran jokaiselle elementille asynkronisessa iteroitavassa. Se *ei* palauta uutta asynkronista iteroitavaa, vaan suorittaa funktion sivuvaikutuksena. Tämä voi olla hyödyllistä esimerkiksi lokitukseen tai käyttöliittymän päivittämiseen.


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

some()

some()-apuohjelma testaa, läpäiseekö vähintään yksi elementti asynkronisessa iteroitavassa annetun funktion toteuttaman testin. Se palauttaa promisen, joka ratkeaa totuusarvoksi (true, jos vähintään yksi elementti täyttää ehdon, muuten 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("Onko parillinen luku:", hasEvenNumber); // Tuloste: Onko parillinen luku: true
})();

every()

every()-apuohjelma testaa, läpäisevätkö kaikki elementit asynkronisessa iteroitavassa annetun funktion toteuttaman testin. Se palauttaa promisen, joka ratkeaa totuusarvoksi (true, jos kaikki elementit täyttävät ehdon, muuten 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("Ovatko kaikki parillisia:", areAllEven); // Tuloste: Ovatko kaikki parillisia: true
})();

find()

find()-apuohjelma palauttaa ensimmäisen elementin asynkronisesta iteroitavasta, joka täyttää annetun testifunktion. Jos mikään arvo ei täytä testifunktiota, palautetaan undefined. Se palauttaa promisen, joka ratkeaa löydetyksi elementiksi tai undefined-arvoksi.


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("Ensimmäinen parillinen luku:", firstEven); // Tuloste: Ensimmäinen parillinen luku: 2
})();

reduce()

reduce()-apuohjelma suorittaa käyttäjän toimittaman "redusoija"-takaisinkutsufunktion jokaiselle asynkronisen iteroitavan elementille järjestyksessä, välittäen edellisen elementin laskutoimituksen palautusarvon. Redusoijan ajamisen lopputulos kaikkien elementtien yli on yksi arvo. Se palauttaa promisen, joka ratkeaa lopulliseksi kertyneeksi arvoksi.


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("Summa:", sum); // Tuloste: Summa: 15
})();

Käytännön esimerkkejä ja käyttötapauksia

Async Iterator Helpers -apuohjelmat ovat arvokkaita monissa eri skenaarioissa. Tutustutaan muutamiin käytännön esimerkkeihin:

1. Datan käsittely suoratoistavasta API:sta

Kuvittele, että rakennat reaaliaikaista datan visualisointinäyttöä, joka vastaanottaa dataa suoratoistavasta API:sta. API lähettää päivityksiä jatkuvasti, ja sinun täytyy käsitellä nämä päivitykset näyttääksesi ajantasaisimmat tiedot.


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);
      // Olettaen, että API lähettää JSON-objekteja rivinvaihdoilla erotettuna
      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'; // Korvaa omalla API-URL:lläsi
const dataStream = fetchDataFromAPI(apiURL);

// Käsittele tietovirta
(async () => {
  for await (const data of dataStream.filter(item => item.type === 'metric').map(item => ({ timestamp: item.timestamp, value: item.value }))) {
    console.log('Käsitelty data:', data);
    // Päivitä näyttö käsitellyllä datalla
  }
})();

Tässä esimerkissä fetchDataFromAPI hakee dataa suoratoistavasta API:sta, jäsentää JSON-objektit ja tuottaa ne asynkronisena iteroitavana. filter-apuohjelma valitsee vain metriikat, ja map-apuohjelma muuntaa datan haluttuun muotoon ennen näytön päivittämistä.

2. Suurten tiedostojen lukeminen ja käsittely

Oletetaan, että sinun täytyy käsitellä suurta asiakasdataa sisältävää CSV-tiedostoa. Sen sijaan, että lataisit koko tiedoston muistiin, voit käyttää Async Iterator Helper -apuohjelmia käsitelläksesi sen pala kerrallaan.


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'; // Korvaa omalla tiedostopolullasi
const lines = readLinesFromFile(filePath);

// Käsittele rivit
(async () => {
  for await (const customerData of lines.drop(1).map(line => line.split(',')).filter(data => data[2] === 'USA')) {
    console.log('Asiakas Yhdysvalloista:', customerData);
    // Käsittele yhdysvaltalaisten asiakkaiden data
  }
})();

Tässä esimerkissä readLinesFromFile lukee tiedoston rivi riviltä ja tuottaa jokaisen rivin asynkronisena iteroitavana. drop(1)-apuohjelma ohittaa otsikkorivin, map-apuohjelma jakaa rivin sarakkeisiin, ja filter-apuohjelma valitsee vain asiakkaat Yhdysvalloista.

3. Reaaliaikaisten tapahtumien käsittely

Async Iterator Helper -apuohjelmia voidaan käyttää myös reaaliaikaisten tapahtumien käsittelyyn lähteistä, kuten WebSockets. Voit luoda asynkronisen iteroitavan, joka lähettää tapahtumia niiden saapuessa, ja sitten käyttää apuohjelmia näiden tapahtumien käsittelyyn.


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); // Ratkaise null-arvolla, kun yhteys sulkeutuu
        }
      });

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

const websocketURL = 'wss://example.com/events'; // Korvaa omalla WebSocket-URL:lläsi
const eventStream = createWebSocketStream(websocketURL);

// Käsittele tapahtumavirta
(async () => {
  for await (const event of eventStream.filter(event => event.type === 'user_login').map(event => ({ userId: event.userId, timestamp: event.timestamp }))) {
    console.log('Käyttäjän kirjautumistapahtuma:', event);
    // Käsittele käyttäjän kirjautumistapahtuma
  }
})();

Tässä esimerkissä createWebSocketStream luo asynkronisen iteroitavan, joka lähettää WebSocketilta vastaanotettuja tapahtumia. filter-apuohjelma valitsee vain käyttäjän kirjautumistapahtumat, ja map-apuohjelma muuntaa datan haluttuun muotoon.

Async Iterator Helper -apuohjelmien käytön hyödyt

Selain- ja ajoympäristötuki

Async Iterator Helpers ovat vielä suhteellisen uusi ominaisuus JavaScriptissä. Vuoden 2024 loppupuolella ne ovat TC39-standardointiprosessin vaiheessa 3, mikä tarkoittaa, että ne todennäköisesti standardoidaan lähitulevaisuudessa. Niitä ei kuitenkaan vielä tueta natiivisti kaikissa selaimissa ja Node.js-versioissa.

Selainyhteensopivuus: Modernit selaimet, kuten Chrome, Firefox, Safari ja Edge, lisäävät vähitellen tukea Async Iterator Helper -apuohjelmille. Voit tarkistaa uusimmat selainyhteensopivuustiedot sivustoilta, kuten Can I use..., nähdäksesi, mitkä selaimet tukevat tätä ominaisuutta.

Node.js-tuki: Viimeisimmät Node.js-versiot (v18 ja uudemmat) tarjoavat kokeellisen tuen Async Iterator Helper -apuohjelmille. Niiden käyttämiseksi saatat joutua ajamaan Node.js:n --experimental-async-iterator -lipulla.

Polyfillit: Jos sinun tarvitsee käyttää Async Iterator Helper -apuohjelmia ympäristöissä, jotka eivät tue niitä natiivisti, voit käyttää polyfilliä. Polyfill on koodinpätkä, joka tarjoaa puuttuvan toiminnallisuuden. Async Iterator Helper -apuohjelmille on saatavilla useita polyfill-kirjastoja; suosittu vaihtoehto on core-js-kirjasto.

Mukautettujen asynkronisten iteraattoreiden toteuttaminen

Vaikka Async Iterator Helper -apuohjelmat tarjoavat kätevän tavan käsitellä olemassa olevia asynkronisia iteroitavia, saatat joskus joutua luomaan omia mukautettuja asynkronisia iteraattoreita. Tämä mahdollistaa datan käsittelyn eri lähteistä, kuten tietokannoista, API:sta tai tiedostojärjestelmistä, suoratoistona.

Luodaksesi mukautetun asynkronisen iteraattorin, sinun on toteutettava @@asyncIterator-metodi objektille. Tämän metodin tulisi palauttaa objekti, jolla on next()-metodi. next()-metodin tulisi palauttaa promise, joka ratkeaa objektiksi, jolla on value- ja done-ominaisuudet.

Tässä on esimerkki mukautetusta asynkronisesta iteraattorista, joka hakee dataa sivutetusta API:sta:


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'; // Korvaa omalla API-URL:lläsi
const paginatedData = fetchPaginatedData(apiBaseURL);

// Käsittele sivutettu data
(async () => {
  for await (const item of paginatedData) {
    console.log('Alkio:', item);
    // Käsittele alkio
  }
})();

Tässä esimerkissä fetchPaginatedData hakee dataa sivutetusta API:sta, tuottaen jokaisen alkion sitä mukaa kun se noudetaan. Asynkroninen iteraattori hoitaa sivutuslogiikan, mikä tekee datan kuluttamisesta suoratoistona helppoa.

Mahdolliset haasteet ja huomioon otettavat seikat

Vaikka Async Iterator Helper -apuohjelmat tarjoavat lukuisia etuja, on tärkeää olla tietoinen joistakin mahdollisista haasteista ja huomioista:

Parhaat käytännöt Async Iterator Helper -apuohjelmien käyttöön

Saadaksesi kaiken irti Async Iterator Helper -apuohjelmista, harkitse seuraavia parhaita käytäntöjä:

Edistyneet tekniikat

Mukautettujen apuohjelmien koostaminen

Voit luoda omia mukautettuja asynkronisia iteraattoriavustajia koostamalla olemassa olevia apuohjelmia tai rakentamalla uusia alusta alkaen. Tämä antaa sinun räätälöidä toiminnallisuutta omiin tarpeisiisi ja luoda uudelleenkäytettäviä komponentteja.


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

// Esimerkkikäyttö:
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);
  }
})();

Useiden asynkronisten iteroitavien yhdistäminen

Voit yhdistää useita asynkronisia iteroitavia yhdeksi asynkroniseksi iteroitavaksi käyttämällä tekniikoita, kuten zip tai merge. Tämä mahdollistaa datan käsittelyn useista lähteistä samanaikaisesti.


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

// Esimerkkikäyttö:
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);
    }
})();

Yhteenveto

JavaScriptin Async Iterator Helper -apuohjelmat tarjoavat tehokkaan ja elegantin tavan käsitellä asynkronisia tietovirtoja. Ne tarjoavat funktionaalisen ja koostettavan lähestymistavan datan manipulointiin, mikä helpottaa monimutkaisten datankäsittelyputkien rakentamista. Ymmärtämällä asynkronisten iteraattoreiden ja iteroitavien peruskäsitteet sekä hallitsemalla eri apumetodeja, voit merkittävästi parantaa asynkronisen JavaScript-koodisi tehokkuutta ja ylläpidettävyyttä. Selain- ja ajoympäristötuen jatkaessa kasvuaan, Async Iterator Helper -apuohjelmista on tulossa olennainen työkalu nykyaikaisille JavaScript-kehittäjille.