Latviešu

Atklājiet JavaScript asinhrono iteratoru palīgus, lai revolucionizētu plūsmu apstrādi. Uzziniet, kā efektīvi apstrādāt asinhronas datu plūsmas, izmantojot map, filter, take, drop un citas metodes.

JavaScript asinhrono iteratoru palīgi: jaudīga plūsmu apstrāde mūsdienu lietojumprogrammām

Mūsdienu JavaScript izstrādē darbs ar asinhronām datu plūsmām ir izplatīta prasība. Neatkarīgi no tā, vai jūs iegūstat datus no API, apstrādājat lielus failus vai apstrādājat reāllaika notikumus, efektīva asinhrono datu pārvaldība ir ļoti svarīga. JavaScript asinhrono iteratoru palīgi (Async Iterator Helpers) nodrošina jaudīgu un elegantu veidu, kā apstrādāt šīs plūsmas, piedāvājot funkcionālu un kompozicionālu pieeju datu manipulācijai.

Kas ir asinhronie iteratori un asinhroni iterējamie objekti?

Pirms iedziļināmies asinhrono iteratoru palīgos, sapratīsim pamatjēdzienus: asinhronos iteratorus (Async Iterators) un asinhroni iterējamos objektus (Async Iterables).

Asinhroni iterējams objekts (Async Iterable) ir objekts, kas definē veidu, kā asinhroni iterēt tā vērtības. To tas dara, implementējot @@asyncIterator metodi, kas atgriež asinhrono iteratoru (Async Iterator).

Asinhronais iterators (Async Iterator) ir objekts, kas nodrošina next() metodi. Šī metode atgriež solījumu (promise), kas atrisinās par objektu ar divām īpašībām:

Šeit ir vienkāršs piemērs:


asynchronous* function generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 500)); // Simulē asinhronu darbību
    yield i;
  }
}

const asyncIterable = generateSequence(5);

(async () => {
  for await (const value of asyncIterable) {
    console.log(value); // Izvade: 1, 2, 3, 4, 5 (ar 500ms aizkavi starp katru)
  }
})();

Šajā piemērā generateSequence ir asinhronā ģeneratora funkcija, kas asinhroni ražo skaitļu secību. Cikls for await...of tiek izmantots, lai patērētu vērtības no asinhroni iterējamā objekta.

Iepazīstinām ar asinhrono iteratoru palīgiem

Asinhrono iteratoru palīgi paplašina asinhrono iteratoru funkcionalitāti, nodrošinot metožu kopumu asinhrono datu plūsmu transformēšanai, filtrēšanai un manipulēšanai. Tie nodrošina funkcionālu un kompozicionālu programmēšanas stilu, atvieglojot sarežģītu datu apstrādes konveijeru izveidi.

Galvenie asinhrono iteratoru palīgi ietver:

Izpētīsim katru palīgu ar piemēriem.

map()

Palīgs map() pārveido katru asinhroni iterējamā objekta elementu, izmantojot norādīto funkciju. Tas atgriež jaunu asinhroni iterējamu objektu ar pārveidotajām vērtībām.


asynchronous* 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); // Izvade: 2, 4, 6, 8, 10 (ar 100ms aizkavi)
  }
})();

Šajā piemērā map(x => x * 2) dubulto katru skaitli secībā.

filter()

Palīgs filter() atlasa elementus no asinhroni iterējamā objekta, pamatojoties uz norādīto nosacījumu (predikāta funkciju). Tas atgriež jaunu asinhroni iterējamu objektu, kas satur tikai tos elementus, kuri atbilst nosacījumam.


asynchronous* 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); // Izvade: 2, 4, 6, 8, 10 (ar 100ms aizkavi)
  }
})();

Šajā piemērā filter(x => x % 2 === 0) atlasa tikai pāra skaitļus no secības.

take()

Palīgs take() atgriež pirmos N elementus no asinhroni iterējamā objekta. Tas atgriež jaunu asinhroni iterējamu objektu, kas satur tikai norādīto elementu skaitu.


asynchronous* 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); // Izvade: 1, 2, 3 (ar 100ms aizkavi)
  }
})();

Šajā piemērā take(3) atlasa pirmos trīs skaitļus no secības.

drop()

Palīgs drop() izlaiž pirmos N elementus no asinhroni iterējamā objekta un atgriež pārējos. Tas atgriež jaunu asinhroni iterējamu objektu, kas satur atlikušos elementus.


asynchronous* 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); // Izvade: 3, 4, 5 (ar 100ms aizkavi)
  }
})();

Šajā piemērā drop(2) izlaiž pirmos divus skaitļus no secības.

toArray()

Palīgs toArray() patērē visu asinhroni iterējamo objektu un apkopo visus elementus masīvā. Tas atgriež solījumu (promise), kas atrisinās par masīvu, kurš satur visus elementus.


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

Šajā piemērā toArray() apkopo visus skaitļus no secības masīvā.

forEach()

Palīgs forEach() izpilda norādīto funkciju vienu reizi katram elementam asinhroni iterējamā objektā. Tas *neatgriež* jaunu asinhroni iterējamu objektu, tas izpilda funkciju ar blakusefektu. Tas var būt noderīgi, veicot darbības, piemēram, reģistrēšanu vai lietotāja saskarnes atjaunināšanu.


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

some()

Palīgs some() pārbauda, vai vismaz viens elements asinhroni iterējamā objektā iztur testu, ko implementē norādītā funkcija. Tas atgriež solījumu (promise), kas atrisinās par Būla vērtību (true, ja vismaz viens elements atbilst nosacījumam, citādi false).


asynchronous* 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); // Izvade: Has even number: true
})();

every()

Palīgs every() pārbauda, vai visi elementi asinhroni iterējamā objektā iztur testu, ko implementē norādītā funkcija. Tas atgriež solījumu (promise), kas atrisinās par Būla vērtību (true, ja visi elementi atbilst nosacījumam, citādi false).


asynchronous* 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); // Izvade: Are all even: true
})();

find()

Palīgs find() atgriež pirmo elementu asinhroni iterējamā objektā, kas atbilst norādītajai pārbaudes funkcijai. Ja neviena vērtība neatbilst pārbaudes funkcijai, tiek atgriezts undefined. Tas atgriež solījumu (promise), kas atrisinās par atrastu elementu vai undefined.


asynchronous* 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); // Izvade: First even number: 2
})();

reduce()

Palīgs reduce() izpilda lietotāja nodrošinātu "reducera" atzvanīšanas funkciju katram asinhroni iterējamā objekta elementam secīgi, nododot atgriezto vērtību no aprēķina par iepriekšējo elementu. Gala rezultāts, izpildot reduceri visiem elementiem, ir viena vērtība. Tas atgriež solījumu (promise), kas atrisinās par galīgo uzkrāto vērtību.


asynchronous* 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); // Izvade: Sum: 15
})();

Praktiski piemēri un lietošanas gadījumi

Asinhrono iteratoru palīgi ir vērtīgi dažādos scenārijos. Apskatīsim dažus praktiskus piemērus:

1. Datu apstrāde no straumēšanas API

Iedomājieties, ka jūs veidojat reāllaika datu vizualizācijas informācijas paneli, kas saņem datus no straumēšanas API. API nepārtraukti sūta atjauninājumus, un jums ir jāapstrādā šie atjauninājumi, lai parādītu jaunāko informāciju.


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

  if (!response.body) {
    throw new Error("ReadableStream netiek atbalstīts šajā vidē");
  }

  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);
      // Pieņemot, ka API sūta JSON objektus, kas atdalīti ar jaunām rindām
      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'; // Aizvietojiet ar savu API URL
const dataStream = fetchDataFromAPI(apiURL);

// Apstrādājiet datu plūsmu
(async () => {
  for await (const data of dataStream.filter(item => item.type === 'metric').map(item => ({ timestamp: item.timestamp, value: item.value }))) {
    console.log('Apstrādātie dati:', data);
    // Atjauniniet informācijas paneli ar apstrādātajiem datiem
  }
})();

Šajā piemērā fetchDataFromAPI iegūst datus no straumēšanas API, parsē JSON objektus un atgriež tos kā asinhroni iterējamu objektu. Palīgs filter atlasa tikai metrikas, un palīgs map pārveido datus vēlamajā formātā pirms informācijas paneļa atjaunināšanas.

2. Lielu failu lasīšana un apstrāde

Pieņemsim, ka jums ir jāapstrādā liels CSV fails, kas satur klientu datus. Tā vietā, lai ielādētu visu failu atmiņā, jūs varat izmantot asinhrono iteratoru palīgus, lai to apstrādātu pa daļām.


asynchronous* 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'; // Aizvietojiet ar savu faila ceļu
const lines = readLinesFromFile(filePath);

// Apstrādājiet rindas
(async () => {
  for await (const customerData of lines.drop(1).map(line => line.split(',')).filter(data => data[2] === 'USA')) {
    console.log('Klients no ASV:', customerData);
    // Apstrādājiet klientu datus no ASV
  }
})();

Šajā piemērā readLinesFromFile lasa failu rindu pa rindai un atgriež katru rindu kā asinhroni iterējamu objektu. Palīgs drop(1) izlaiž galvenes rindu, palīgs map sadala rindu kolonnās, un palīgs filter atlasa tikai klientus no ASV.

3. Reāllaika notikumu apstrāde

Asinhrono iteratoru palīgus var izmantot arī, lai apstrādātu reāllaika notikumus no avotiem, piemēram, WebSockets. Jūs varat izveidot asinhroni iterējamu objektu, kas emitē notikumus, kad tie pienāk, un pēc tam izmantot palīgus, lai apstrādātu šos notikumus.


asynchronous* 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); // Atrisina ar null, kad savienojums tiek aizvērts
        }
      });

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

const websocketURL = 'wss://example.com/events'; // Aizvietojiet ar savu WebSocket URL
const eventStream = createWebSocketStream(websocketURL);

// Apstrādājiet notikumu plūsmu
(async () => {
  for await (const event of eventStream.filter(event => event.type === 'user_login').map(event => ({ userId: event.userId, timestamp: event.timestamp }))) {
    console.log('Lietotāja pieteikšanās notikums:', event);
    // Apstrādājiet lietotāja pieteikšanās notikumu
  }
})();

Šajā piemērā createWebSocketStream izveido asinhroni iterējamu objektu, kas emitē notikumus, kas saņemti no WebSocket. Palīgs filter atlasa tikai lietotāju pieteikšanās notikumus, un palīgs map pārveido datus vēlamajā formātā.

Asinhrono iteratoru palīgu izmantošanas priekšrocības

Pārlūkprogrammu un izpildes vides atbalsts

Asinhrono iteratoru palīgi joprojām ir salīdzinoši jauna funkcija JavaScript. Sākot ar 2024. gada beigām, tie ir TC39 standartizācijas procesa 3. posmā, kas nozīmē, ka tie, visticamāk, tiks standartizēti tuvākajā nākotnē. Tomēr tie vēl nav dabiski atbalstīti visās pārlūkprogrammās un Node.js versijās.

Pārlūkprogrammu atbalsts: Mūsdienu pārlūkprogrammas, piemēram, Chrome, Firefox, Safari un Edge, pakāpeniski pievieno atbalstu asinhrono iteratoru palīgiem. Jūs varat pārbaudīt jaunāko pārlūkprogrammu saderības informāciju tīmekļa vietnēs, piemēram, Can I use..., lai redzētu, kuras pārlūkprogrammas atbalsta šo funkciju.

Node.js atbalsts: Jaunākās Node.js versijas (v18 un jaunākas) nodrošina eksperimentālu atbalstu asinhrono iteratoru palīgiem. Lai tos izmantotu, jums, iespējams, būs jāpalaiž Node.js ar karodziņu --experimental-async-iterator.

Polifili (Polyfills): Ja jums ir nepieciešams izmantot asinhrono iteratoru palīgus vidēs, kas tos dabiski neatbalsta, varat izmantot polifilu. Polifils ir koda fragments, kas nodrošina trūkstošo funkcionalitāti. Ir pieejamas vairākas polifilu bibliotēkas asinhrono iteratoru palīgiem; populāra opcija ir core-js bibliotēka.

Pielāgotu asinhrono iteratoru implementēšana

Lai gan asinhrono iteratoru palīgi nodrošina ērtu veidu, kā apstrādāt esošus asinhroni iterējamus objektus, dažreiz jums var būt nepieciešams izveidot savus pielāgotus asinhronos iteratorus. Tas ļauj jums apstrādāt datus no dažādiem avotiem, piemēram, datu bāzēm, API vai failu sistēmām, straumēšanas veidā.

Lai izveidotu pielāgotu asinhrono iteratoru, jums ir jāimplementē @@asyncIterator metode objektā. Šai metodei ir jāatgriež objekts ar next() metodi. next() metodei ir jāatgriež solījums (promise), kas atrisinās par objektu ar value un done īpašībām.

Šeit ir piemērs pielāgotam asinhronajam iteratoram, kas iegūst datus no lapota API:


asynchronous* 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'; // Aizvietojiet ar savu API URL
const paginatedData = fetchPaginatedData(apiBaseURL);

// Apstrādājiet lapotos datus
(async () => {
  for await (const item of paginatedData) {
    console.log('Elements:', item);
    // Apstrādājiet elementu
  }
})();

Šajā piemērā fetchPaginatedData iegūst datus no lapota API, atgriežot katru elementu, tiklīdz tas tiek iegūts. Asinhronais iterators pārvalda lapošanas loģiku, atvieglojot datu patērēšanu straumēšanas veidā.

Potenciālie izaicinājumi un apsvērumi

Lai gan asinhrono iteratoru palīgi piedāvā daudzas priekšrocības, ir svarīgi apzināties dažus potenciālos izaicinājumus un apsvērumus:

Labākās prakses asinhrono iteratoru palīgu izmantošanai

Lai maksimāli izmantotu asinhrono iteratoru palīgus, ņemiet vērā šādas labākās prakses:

Papildu tehnikas

Pielāgotu palīgu veidošana

Jūs varat izveidot savus pielāgotos asinhrono iteratoru palīgus, apvienojot esošos palīgus vai veidojot jaunus no nulles. Tas ļauj jums pielāgot funkcionalitāti savām specifiskajām vajadzībām un izveidot atkārtoti lietojamus komponentus.


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

// Lietošanas piemērs:
asynchronous* 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);
  }
})();

Vairāku asinhroni iterējamu objektu apvienošana

Jūs varat apvienot vairākus asinhroni iterējamus objektus vienā asinhroni iterējamā objektā, izmantojot tādas tehnikas kā zip vai merge. Tas ļauj vienlaikus apstrādāt datus no vairākiem avotiem.


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

// Lietošanas piemērs:
asynchronous* function generateSequence1(end) {
    for (let i = 1; i <= end; i++) {
        yield i;
    }
}

asynchronous* 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);
    }
})();

Noslēgums

JavaScript asinhrono iteratoru palīgi nodrošina jaudīgu un elegantu veidu, kā apstrādāt asinhronas datu plūsmas. Tie piedāvā funkcionālu un kompozicionālu pieeju datu manipulācijai, atvieglojot sarežģītu datu apstrādes konveijeru izveidi. Izprotot asinhrono iteratoru un asinhroni iterējamo objektu pamatjēdzienus un apgūstot dažādas palīgmetodes, jūs varat ievērojami uzlabot sava asinhronā JavaScript koda efektivitāti un uzturamību. Tā kā pārlūkprogrammu un izpildes vides atbalsts turpina pieaugt, asinhrono iteratoru palīgi kļūs par būtisku rīku mūsdienu JavaScript izstrādātājiem.