Lietuvių

Išnagrinėkite JavaScript asinchroninių iteratorių pagalbines priemones, kad pakeistumėte srautų apdorojimą. Sužinokite, kaip efektyviai valdyti asinchroninius duomenų srautus naudojant map, filter, take, drop ir kt.

JavaScript asinchroninių iteratorių pagalbinės priemonės: galingas srautų apdorojimas šiuolaikinėms programoms

Šiuolaikinėje JavaScript kūrimo praktikoje darbas su asinchroniniais duomenų srautais yra įprastas reikalavimas. Nesvarbu, ar gaunate duomenis iš API, apdorojate didelius failus, ar tvarkote realaus laiko įvykius, efektyvus asinchroninių duomenų valdymas yra labai svarbus. JavaScript asinchroninių iteratorių pagalbinės priemonės (Async Iterator Helpers) suteikia galingą ir elegantišką būdą apdoroti šiuos srautus, siūlydamos funkcinį ir komponuojamą požiūrį į duomenų manipuliavimą.

Kas yra asinchroniniai iteratoriai ir asinchroniniai iteruojami objektai?

Prieš pradedant gilintis į asinchroninių iteratorių pagalbines priemones, supraskime pagrindines sąvokas: asinchroninius iteratorius (Async Iterators) ir asinchroninius iteruojamus objektus (Async Iterables).

Asinchroninis iteruojamas objektas (Async Iterable) yra objektas, apibrėžiantis būdą, kaip asinchroniškai iteruoti per jo reikšmes. Jis tai daro įgyvendindamas @@asyncIterator metodą, kuris grąžina asinchroninį iteratorių (Async Iterator).

Asinchroninis iteratorius (Async Iterator) yra objektas, turintis next() metodą. Šis metodas grąžina pažadą (promise), kuris išsipildo į objektą su dviem savybėmis:

Štai paprastas pavyzdys:


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 500)); // Imituojama asinchroninė operacija
    yield i;
  }
}

const asyncIterable = generateSequence(5);

(async () => {
  for await (const value of asyncIterable) {
    console.log(value); // Išvestis: 1, 2, 3, 4, 5 (su 500ms vėlavimu tarp kiekvieno)
  }
})();

Šiame pavyzdyje generateSequence yra asinchroninė generatoriaus funkcija, kuri asinchroniškai generuoja skaičių seką. for await...of ciklas naudojamas reikšmėms iš asinchroninio iteruojamo objekto gauti.

Pristatome asinchroninių iteratorių pagalbines priemones

Asinchroninių iteratorių pagalbinės priemonės praplečia asinchroninių iteratorių funkcionalumą, suteikdamos metodų rinkinį asinchroniniams duomenų srautams transformuoti, filtruoti ir manipuliuoti. Jos įgalina funkcinį ir komponuojamą programavimo stilių, palengvinantį sudėtingų duomenų apdorojimo grandinių kūrimą.

Pagrindinės asinchroninių iteratorių pagalbinės priemonės apima:

Panagrinėkime kiekvieną pagalbinę priemonę su pavyzdžiais.

map()

map() pagalbinė priemonė transformuoja kiekvieną asinchroninio iteruojamo objekto elementą naudojant pateiktą funkciją. Ji grąžina naują asinchroninį iteruojamą objektą su transformuotomis reikšmėmis.


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); // Išvestis: 2, 4, 6, 8, 10 (su 100ms vėlavimu)
  }
})();

Šiame pavyzdyje map(x => x * 2) padvigubina kiekvieną skaičių sekoje.

filter()

filter() pagalbinė priemonė atrenka elementus iš asinchroninio iteruojamo objekto pagal pateiktą sąlygą (predikato funkciją). Ji grąžina naują asinchroninį iteruojamą objektą, kuriame yra tik tie elementai, kurie atitinka sąlygą.


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); // Išvestis: 2, 4, 6, 8, 10 (su 100ms vėlavimu)
  }
})();

Šiame pavyzdyje filter(x => x % 2 === 0) atrenka tik lyginius skaičius iš sekos.

take()

take() pagalbinė priemonė grąžina pirmuosius N elementų iš asinchroninio iteruojamo objekto. Ji grąžina naują asinchroninį iteruojamą objektą, kuriame yra tik nurodytas elementų skaičius.


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); // Išvestis: 1, 2, 3 (su 100ms vėlavimu)
  }
})();

Šiame pavyzdyje take(3) atrenka pirmuosius tris skaičius iš sekos.

drop()

drop() pagalbinė priemonė praleidžia pirmuosius N elementų iš asinchroninio iteruojamo objekto ir grąžina likusius. Ji grąžina naują asinchroninį iteruojamą objektą, kuriame yra likę elementai.


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); // Išvestis: 3, 4, 5 (su 100ms vėlavimu)
  }
})();

Šiame pavyzdyje drop(2) praleidžia pirmuosius du skaičius iš sekos.

toArray()

toArray() pagalbinė priemonė apdoroja visą asinchroninį iteruojamą objektą ir surenka visus elementus į masyvą. Ji grąžina pažadą (promise), kuris išsipildo į masyvą, kuriame yra visi elementai.


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

Šiame pavyzdyje toArray() surenka visus skaičius iš sekos į masyvą.

forEach()

forEach() pagalbinė priemonė kiekvienam asinchroninio iteruojamo objekto elementui vieną kartą įvykdo pateiktą funkciją. Ji *negrąžina* naujo asinchroninio iteruojamo objekto, o įvykdo funkciją dėl jos šalutinio poveikio. Tai gali būti naudinga atliekant tokias operacijas kaip registravimas (logging) ar vartotojo sąsajos atnaujinimas.


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

some()

some() pagalbinė priemonė patikrina, ar bent vienas elementas asinchroniniame iteruojamame objekte atitinka pateiktos funkcijos testą. Ji grąžina pažadą (promise), kuris išsipildo į loginę reikšmę (true, jei bent vienas elementas atitinka sąlygą, kitu atveju - 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); // Išvestis: Has even number: true
})();

every()

every() pagalbinė priemonė patikrina, ar visi elementai asinchroniniame iteruojamame objekte atitinka pateiktos funkcijos testą. Ji grąžina pažadą (promise), kuris išsipildo į loginę reikšmę (true, jei visi elementai atitinka sąlygą, kitu atveju - 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); // Išvestis: Are all even: true
})();

find()

find() pagalbinė priemonė grąžina pirmąjį elementą asinchroniniame iteruojamame objekte, kuris atitinka pateiktą testavimo funkciją. Jei jokia reikšmė neatitinka testavimo funkcijos, grąžinama undefined. Ji grąžina pažadą (promise), kuris išsipildo į rastą elementą arba 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); // Išvestis: First even number: 2
})();

reduce()

reduce() pagalbinė priemonė vykdo vartotojo pateiktą „reduktoriaus“ (reducer) atgalinio iškvietimo funkciją kiekvienam asinchroninio iteruojamo objekto elementui, perduodant grąžinamąją reikšmę iš skaičiavimo su ankstesniu elementu. Galutinis reduktoriaus vykdymo rezultatas visuose elementuose yra viena reikšmė. Ji grąžina pažadą (promise), kuris išsipildo į galutinę sukauptą reikšmę.


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); // Išvestis: Sum: 15
})();

Praktiniai pavyzdžiai ir naudojimo atvejai

Asinchroninių iteratorių pagalbinės priemonės yra vertingos įvairiuose scenarijuose. Panagrinėkime keletą praktinių pavyzdžių:

1. Duomenų apdorojimas iš srautinio API

Įsivaizduokite, kad kuriate realaus laiko duomenų vizualizacijos prietaisų skydelį, kuris gauna duomenis iš srautinio API. API nuolat siunčia atnaujinimus, o jums reikia apdoroti šiuos atnaujinimus, kad būtų rodoma naujausia informacija.


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);
      // Darant prielaidą, kad API siunčia JSON objektus, atskirtus naujomis eilutėmis
      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'; // Pakeiskite savo API URL
const dataStream = fetchDataFromAPI(apiURL);

// Apdorokite duomenų srautą
(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);
    // Atnaujinkite prietaisų skydelį su apdorotais duomenimis
  }
})();

Šiame pavyzdyje fetchDataFromAPI gauna duomenis iš srautinio API, išanalizuoja JSON objektus ir pateikia juos kaip asinchroninį iteruojamą objektą. filter pagalbinė priemonė atrenka tik metrikas, o map pagalbinė priemonė transformuoja duomenis į norimą formatą prieš atnaujinant prietaisų skydelį.

2. Didelių failų skaitymas ir apdorojimas

Tarkime, jums reikia apdoroti didelį CSV failą, kuriame yra klientų duomenys. Užuot įkėlus visą failą į atmintį, galite naudoti asinchroninių iteratorių pagalbines priemones, kad apdorotumėte jį dalimis.


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'; // Pakeiskite savo failo keliu
const lines = readLinesFromFile(filePath);

// Apdorokite eilutes
(async () => {
  for await (const customerData of lines.drop(1).map(line => line.split(',')).filter(data => data[2] === 'USA')) {
    console.log('Customer from USA:', customerData);
    // Apdorokite klientų duomenis iš JAV
  }
})();

Šiame pavyzdyje readLinesFromFile skaito failą eilutę po eilutės ir pateikia kiekvieną eilutę kaip asinchroninį iteruojamą objektą. drop(1) pagalbinė priemonė praleidžia antraštės eilutę, map pagalbinė priemonė padalija eilutę į stulpelius, o filter pagalbinė priemonė atrenka tik klientus iš JAV.

3. Realaus laiko įvykių tvarkymas

Asinchroninių iteratorių pagalbinės priemonės taip pat gali būti naudojamos realaus laiko įvykiams iš tokių šaltinių kaip „WebSockets“ tvarkyti. Galite sukurti asinchroninį iteruojamą objektą, kuris skleidžia įvykius, kai jie atvyksta, ir tada naudoti pagalbines priemones šiems įvykiams apdoroti.


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); // Išsipildo su null, kai ryšys nutrūksta
        }
      });

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

const websocketURL = 'wss://example.com/events'; // Pakeiskite savo WebSocket URL
const eventStream = createWebSocketStream(websocketURL);

// Apdorokite įvykių srautą
(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);
    // Apdorokite vartotojo prisijungimo įvykį
  }
})();

Šiame pavyzdyje createWebSocketStream sukuria asinchroninį iteruojamą objektą, kuris skleidžia įvykius, gautus iš „WebSocket“. filter pagalbinė priemonė atrenka tik vartotojų prisijungimo įvykius, o map pagalbinė priemonė transformuoja duomenis į norimą formatą.

Asinchroninių iteratorių pagalbinių priemonių naudojimo privalumai

Naršyklių ir vykdymo aplinkos palaikymas

Asinchroninių iteratorių pagalbinės priemonės vis dar yra palyginti nauja JavaScript funkcija. 2024 m. pabaigoje jos yra 3-iame TC39 standartizavimo proceso etape, o tai reiškia, kad artimiausiu metu jos greičiausiai bus standartizuotos. Tačiau jos dar nėra natūraliai palaikomos visose naršyklėse ir Node.js versijose.

Naršyklių palaikymas: Šiuolaikinės naršyklės, tokios kaip Chrome, Firefox, Safari ir Edge, palaipsniui prideda asinchroninių iteratorių pagalbinių priemonių palaikymą. Naujausią informaciją apie naršyklių suderinamumą galite patikrinti svetainėse, tokiose kaip „Can I use...“, kad sužinotumėte, kurios naršyklės palaiko šią funkciją.

Node.js palaikymas: Naujesnės Node.js versijos (v18 ir naujesnės) suteikia eksperimentinį asinchroninių iteratorių pagalbinių priemonių palaikymą. Norėdami jas naudoti, gali tekti paleisti Node.js su --experimental-async-iterator vėliava.

Polifilai (Polyfills): Jei reikia naudoti asinchroninių iteratorių pagalbines priemones aplinkose, kurios jų natūraliai nepalaiko, galite naudoti polifilą. Polifilas yra kodo dalis, kuri suteikia trūkstamą funkcionalumą. Yra keletas polifilų bibliotekų, skirtų asinchroninių iteratorių pagalbinėms priemonėms; populiarus pasirinkimas yra core-js biblioteka.

Individualių asinchroninių iteratorių įgyvendinimas

Nors asinchroninių iteratorių pagalbinės priemonės suteikia patogų būdą apdoroti esamus asinchroninius iteruojamus objektus, kartais gali prireikti sukurti savo individualius asinchroninius iteratorius. Tai leidžia jums srautiniu būdu tvarkyti duomenis iš įvairių šaltinių, tokių kaip duomenų bazės, API ar failų sistemos.

Norėdami sukurti individualų asinchroninį iteratorių, turite objekte įgyvendinti @@asyncIterator metodą. Šis metodas turėtų grąžinti objektą su next() metodu. next() metodas turėtų grąžinti pažadą (promise), kuris išsipildo į objektą su value ir done savybėmis.

Štai pavyzdys individualaus asinchroninio iteratoriaus, kuris gauna duomenis iš puslapiuojamo API:


asyn 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'; // Pakeiskite savo API URL
const paginatedData = fetchPaginatedData(apiBaseURL);

// Apdorokite puslapiuojamus duomenis
(async () => {
  for await (const item of paginatedData) {
    console.log('Item:', item);
    // Apdorokite elementą
  }
})();

Šiame pavyzdyje fetchPaginatedData gauna duomenis iš puslapiuojamo API, pateikdamas kiekvieną elementą, kai jis gaunamas. Asinchroninis iteratorius tvarko puslapiavimo logiką, todėl duomenis lengva apdoroti srautiniu būdu.

Galimi iššūkiai ir svarstymai

Nors asinchroninių iteratorių pagalbinės priemonės siūlo daugybę privalumų, svarbu žinoti apie kai kuriuos galimus iššūkius ir svarstymus:

Geriausios praktikos naudojant asinchroninių iteratorių pagalbines priemones

Norėdami gauti kuo daugiau naudos iš asinchroninių iteratorių pagalbinių priemonių, atsižvelkite į šias geriausias praktikas:

Pažangios technikos

Individualių pagalbinių priemonių kūrimas

Galite kurti savo individualias asinchroninių iteratorių pagalbines priemones, komponuodami esamas arba kurdami naujas nuo nulio. Tai leidžia pritaikyti funkcionalumą savo specifiniams poreikiams ir kurti pakartotinai naudojamus komponentus.


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

// Naudojimo pavyzdys:
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);
  }
})();

Kelių asinchroninių iteruojamų objektų sujungimas

Galite sujungti kelis asinchroninius iteruojamus objektus į vieną, naudodami tokias technikas kaip zip ar merge. Tai leidžia vienu metu apdoroti duomenis iš kelių šaltinių.


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

// Naudojimo pavyzdys:
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);
    }
})();

Išvada

JavaScript asinchroninių iteratorių pagalbinės priemonės suteikia galingą ir elegantišką būdą apdoroti asinchroninius duomenų srautus. Jos siūlo funkcinį ir komponuojamą požiūrį į duomenų manipuliavimą, todėl lengviau kurti sudėtingas duomenų apdorojimo grandines. Suprasdami pagrindines asinchroninių iteratorių ir iteruojamų objektų sąvokas bei įvaldę įvairius pagalbinius metodus, galite žymiai pagerinti savo asinchroninio JavaScript kodo efektyvumą ir palaikomumą. Didėjant naršyklių ir vykdymo aplinkos palaikymui, asinchroninių iteratorių pagalbinės priemonės taps esminiu įrankiu šiuolaikiniams JavaScript kūrėjams.