Eesti

Avastage JavaScripti asünkroonsed iteraatoriabilised, et muuta voogude töötlemine revolutsiooniliseks. Õppige, kuidas tõhusalt käsitleda asünkroonseid andmevooge meetoditega map, filter, take, drop ja teised.

JavaScripti asünkroonsed iteraatoriabilised: võimas voogude töötlemine kaasaegsetes rakendustes

Kaasaegses JavaScripti arenduses on asünkroonsete andmevoogudega tegelemine tavaline nõue. Olgu tegemist andmete hankimisega API-st, suurte failide töötlemisega või reaalajas sündmuste käsitlemisega, asünkroonsete andmete tõhus haldamine on ülioluline. JavaScripti asünkroonsed iteraatoriabilised pakuvad võimsat ja elegantset viisi nende voogude töötlemiseks, pakkudes funktsionaalset ja komponeeritavat lähenemist andmete manipuleerimisele.

Mis on asünkroonsed iteraatorid ja asünkroonsed itereeritavad?

Enne asünkroonsete iteraatoriabiliste juurde sukeldumist mõistame aluseks olevaid kontseptsioone: asünkroonsed iteraatorid ja asünkroonsed itereeritavad.

Asünkroonne itereeritav on objekt, mis määratleb viisi oma väärtuste asünkroonseks itereerimiseks. See teeb seda, implementeerides meetodi @@asyncIterator, mis tagastab asünkroonse iteraatori.

Asünkroonne iteraator on objekt, mis pakub meetodit next(). See meetod tagastab lubaduse (promise), mis laheneb objektiks, millel on kaks omadust:

Siin on lihtne näide:


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 500)); // Simuleerib asünkroonset operatsiooni
    yield i;
  }
}

const asyncIterable = generateSequence(5);

(async () => {
  for await (const value of asyncIterable) {
    console.log(value); // Väljund: 1, 2, 3, 4, 5 (igaühe vahel 500ms viivitus)
  }
})();

Selles näites on generateSequence asünkroonne generaatorfunktsioon, mis toodab asünkroonselt numbrite jada. for await...of tsüklit kasutatakse väärtuste tarbimiseks asünkroonsest itereeritavast.

Asünkroonsete iteraatoriabiliste tutvustus

Asünkroonsed iteraatoriabilised laiendavad asünkroonsete iteraatorite funktsionaalsust, pakkudes meetodite komplekti asünkroonsete andmevoogude teisendamiseks, filtreerimiseks ja manipuleerimiseks. Need võimaldavad funktsionaalset ja komponeeritavat programmeerimisstiili, mis teeb keerukate andmetöötlusahelate ehitamise lihtsamaks.

Peamised asünkroonsed iteraatoriabilised on järgmised:

Uurime iga abilist näidetega.

map()

Abiline map() teisendab asünkroonse itereeritava iga elemendi, kasutades antud funktsiooni. See tagastab uue asünkroonse itereeritava teisendatud väärtustega.


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

Selles näites map(x => x * 2) kahekordistab iga numbri järjestuses.

filter()

Abiline filter() valib asünkroonsest itereeritavast elemente antud tingimuse (predikaatfunktsiooni) alusel. See tagastab uue asünkroonse itereeritava, mis sisaldab ainult tingimusele vastavaid elemente.


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

Selles näites filter(x => x % 2 === 0) valib järjestusest ainult paarisarvud.

take()

Abiline take() tagastab asünkroonsest itereeritavast esimesed N elementi. See tagastab uue asünkroonse itereeritava, mis sisaldab ainult määratud arvu elemente.


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

Selles näites valib take(3) järjestusest esimesed kolm numbrit.

drop()

Abiline drop() jätab asünkroonsest itereeritavast vahele esimesed N elementi ja tagastab ülejäänud. See tagastab uue asünkroonse itereeritava, mis sisaldab allesjäänud elemente.


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

Selles näites jätab drop(2) järjestusest vahele esimesed kaks numbrit.

toArray()

Abiline toArray() tarbib kogu asünkroonse itereeritava ja kogub kõik elemendid massiivi. See tagastab lubaduse, mis laheneb massiiviga, mis sisaldab kõiki elemente.


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

Selles näites kogub toArray() kõik numbrid järjestusest massiivi.

forEach()

Abiline forEach() käivitab antud funktsiooni iga asünkroonse itereeritava elemendi jaoks. See *ei* tagasta uut asünkroonset itereeritavat, vaid täidab funktsiooni kõrvalmõjuna. See võib olla kasulik selliste toimingute tegemiseks nagu logimine või kasutajaliidese uuendamine.


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

some()

Abiline some() testib, kas vähemalt üks element asünkroonses itereeritavas läbib antud funktsiooni poolt implementeeritud testi. See tagastab lubaduse, mis laheneb tõeväärtuseks (true, kui vähemalt üks element vastab tingimusele, vastasel juhul 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); // Väljund: Has even number: true
})();

every()

Abiline every() testib, kas kõik elemendid asünkroonses itereeritavas läbivad antud funktsiooni poolt implementeeritud testi. See tagastab lubaduse, mis laheneb tõeväärtuseks (true, kui kõik elemendid vastavad tingimusele, vastasel juhul 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); // Väljund: Are all even: true
})();

find()

Abiline find() tagastab esimese elemendi asünkroonses itereeritavas, mis vastab antud testimisfunktsioonile. Kui ükski väärtus ei vasta testimisfunktsioonile, tagastatakse undefined. See tagastab lubaduse, mis laheneb leitud elemendi või undefined-iga.


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); // Väljund: First even number: 2
})();

reduce()

Abiline reduce() käivitab kasutaja poolt antud "reducer" tagasikutse funktsiooni iga asünkroonse itereeritava elemendi peal, järjekorras, andes edasi eelmise elemendi arvutuse tagastusväärtuse. Reduktori käivitamise lõpptulemus kõigi elementide peal on üks väärtus. See tagastab lubaduse, mis laheneb lõpliku akumuleeritud väärtusega.


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); // Väljund: Sum: 15
})();

Praktilised näited ja kasutusjuhud

Asünkroonsed iteraatoriabilised on väärtuslikud mitmesugustes stsenaariumides. Uurime mõningaid praktilisi näiteid:

1. Andmete töötlemine voogesituse API-st

Kujutage ette, et ehitate reaalajas andmete visualiseerimise armatuurlauda, mis saab andmeid voogesituse API-st. API saadab pidevalt uuendusi ja peate neid uuendusi töötlema, et kuvada uusimat teavet.


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

  if (!response.body) {
    throw new Error("ReadableStream ei ole selles keskkonnas toetatud");
  }

  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);
      // Eeldades, et API saadab reavahetusega eraldatud JSON-objekte
      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'; // Asendage oma API URL-iga
const dataStream = fetchDataFromAPI(apiURL);

// Andmevoo töötlemine
(async () => {
  for await (const data of dataStream.filter(item => item.type === 'metric').map(item => ({ timestamp: item.timestamp, value: item.value }))) {
    console.log('Töödeldud andmed:', data);
    // Uuendage armatuurlauda töödeldud andmetega
  }
})();

Selles näites hangib fetchDataFromAPI andmeid voogesituse API-st, parssib JSON-objekte ja annab need edasi asünkroonse itereeritavana. Abiline filter valib ainult mõõdikud ja map teisendab andmed soovitud vormingusse enne armatuurlaua uuendamist.

2. Suurte failide lugemine ja töötlemine

Oletame, et peate töötlema suurt kliendiandmeid sisaldavat CSV-faili. Selle asemel, et laadida kogu fail mällu, saate seda töödelda tükk-haaval, kasutades asünkroonseid iteraatoriabilisi.


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'; // Asendage oma faili teega
const lines = readLinesFromFile(filePath);

// Ridade töötlemine
(async () => {
  for await (const customerData of lines.drop(1).map(line => line.split(',')).filter(data => data[2] === 'USA')) {
    console.log('Klient USA-st:', customerData);
    // Töötle USA kliendiandmeid
  }
})();

Selles näites loeb readLinesFromFile faili rida-realt ja annab iga rea edasi asünkroonse itereeritavana. Abiline drop(1) jätab vahele päiserea, map jagab rea veergudeks ja filter valib ainult USA-st pärit kliendid.

3. Reaalajas sündmuste käsitlemine

Asünkroonseid iteraatoriabilisi saab kasutada ka reaalajas sündmuste käsitlemiseks allikatest nagu WebSocketid. Saate luua asünkroonse itereeritava, mis väljastab sündmusi nende saabumisel, ja seejärel kasutada abilisi nende sündmuste töötlemiseks.


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); // Lahenda nulliga, kui ühendus sulgub
        }
      });

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

const websocketURL = 'wss://example.com/events'; // Asendage oma WebSocketi URL-iga
const eventStream = createWebSocketStream(websocketURL);

// Sündmustevoo töötlemine
(async () => {
  for await (const event of eventStream.filter(event => event.type === 'user_login').map(event => ({ userId: event.userId, timestamp: event.timestamp }))) {
    console.log('Kasutaja sisselogimise sündmus:', event);
    // Töötle kasutaja sisselogimise sündmust
  }
})();

Selles näites loob createWebSocketStream asünkroonse itereeritava, mis väljastab WebSocketist saadud sündmusi. Abiline filter valib ainult kasutajate sisselogimise sündmused ja map teisendab andmed soovitud vormingusse.

Asünkroonsete iteraatoriabiliste kasutamise eelised

Brauseri ja käituskeskkonna tugi

Asünkroonsed iteraatoriabilised on JavaScriptis veel suhteliselt uus funktsioon. 2024. aasta lõpu seisuga on nad TC39 standardimisprotsessi 3. etapis, mis tähendab, et tõenäoliselt standarditakse need lähitulevikus. Siiski ei ole need veel kõigis brauserites ja Node.js-i versioonides algupäraselt toetatud.

Brauseri tugi: Kaasaegsed brauserid nagu Chrome, Firefox, Safari ja Edge lisavad järk-järgult tuge asünkroonsetele iteraatoriabilistele. Saate kontrollida uusimat brauserite ühilduvuse teavet veebisaitidelt nagu Can I use..., et näha, millised brauserid seda funktsiooni toetavad.

Node.js tugi: Node.js-i uuemad versioonid (v18 ja uuemad) pakuvad eksperimentaalset tuge asünkroonsetele iteraatoriabilistele. Nende kasutamiseks peate võib-olla käivitama Node.js-i lipuga --experimental-async-iterator.

Polüfillid: Kui peate kasutama asünkroonseid iteraatoriabilisi keskkondades, mis neid algupäraselt ei toeta, saate kasutada polüfilli. Polüfill on koodijupp, mis pakub puuduvat funktsionaalsust. Asünkroonsete iteraatoriabiliste jaoks on saadaval mitu polüfilli teeki; populaarne valik on teek core-js.

Kohandatud asünkroonsete iteraatorite implementeerimine

Kuigi asünkroonsed iteraatoriabilised pakuvad mugavat viisi olemasolevate asünkroonsete itereeritavate töötlemiseks, peate mõnikord looma oma kohandatud asünkroonseid iteraatoreid. See võimaldab teil käsitleda andmeid erinevatest allikatest, näiteks andmebaasidest, API-dest või failisüsteemidest, voogesituse viisil.

Kohandatud asünkroonse iteraatori loomiseks peate objektil implementeerima meetodi @@asyncIterator. See meetod peaks tagastama objekti meetodiga next(). Meetod next() peaks tagastama lubaduse, mis laheneb objektiks, millel on omadused value ja done.

Siin on näide kohandatud asünkroonsest iteraatorist, mis hangib andmeid lehekülgedeks jaotatud API-st:


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'; // Asendage oma API URL-iga
const paginatedData = fetchPaginatedData(apiBaseURL);

// Lehekülgedeks jaotatud andmete töötlemine
(async () => {
  for await (const item of paginatedData) {
    console.log('Element:', item);
    // Töötle elementi
  }
})();

Selles näites hangib fetchPaginatedData andmeid lehekülgedeks jaotatud API-st, andes iga elemendi edasi selle kättesaamisel. Asünkroonne iteraator tegeleb lehekülgede loogikaga, mis teeb andmete tarbimise voogesituse viisil lihtsaks.

Võimalikud väljakutsed ja kaalutlused

Kuigi asünkroonsed iteraatoriabilised pakuvad arvukalt eeliseid, on oluline olla teadlik mõningatest võimalikest väljakutsetest ja kaalutlustest:

Parimad praktikad asünkroonsete iteraatoriabiliste kasutamiseks

Et asünkroonsetest iteraatoriabilistest maksimumi võtta, kaaluge järgmisi parimaid praktikaid:

Edasijõudnud tehnikad

Kohandatud abiliste komponeerimine

Saate luua oma kohandatud asünkroonseid iteraatoriabilisi, komponeerides olemasolevaid abilisi või ehitades uusi nullist. See võimaldab teil kohandada funktsionaalsust oma konkreetsetele vajadustele ja luua taaskasutatavaid komponente.


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

// Kasutusnäide:
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);
  }
})();

Mitme asünkroonse itereeritava kombineerimine

Saate kombineerida mitu asünkroonset itereeritavat üheks asünkroonseks itereeritavaks, kasutades tehnikaid nagu zip või merge. See võimaldab teil töödelda andmeid mitmest allikast samaaegselt.


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

// Kasutusnäide:
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);
    }
})();

Kokkuvõte

JavaScripti asünkroonsed iteraatoriabilised pakuvad võimsat ja elegantset viisi asünkroonsete andmevoogude töötlemiseks. Need pakuvad funktsionaalset ja komponeeritavat lähenemist andmete manipuleerimisele, mis teeb keerukate andmetöötlusahelate ehitamise lihtsamaks. Mõistes asünkroonsete iteraatorite ja itereeritavate põhimõtteid ning omandades erinevaid abimeetodeid, saate märkimisväärselt parandada oma asünkroonse JavaScripti koodi tõhusust ja hooldatavust. Kuna brauserite ja käituskeskkondade tugi jätkuvalt kasvab, on asünkroonsetest iteraatoriabilistest saamas kaasaegsete JavaScripti arendajate jaoks hädavajalik tööriist.