Hrvatski

Istražite JavaScript Async Iterator Helpers za revolucionarnu obradu tokova. Naučite kako učinkovito rukovati asinkronim tokovima podataka pomoću map, filter, take, drop i drugih.

JavaScript Async Iterator Helpers: Moćna obrada tokova za moderne aplikacije

U modernom JavaScript razvoju, rad s asinkronim tokovima podataka čest je zahtjev. Bilo da dohvaćate podatke s API-ja, obrađujete velike datoteke ili rukujete događajima u stvarnom vremenu, učinkovito upravljanje asinkronim podacima je ključno. JavaScriptovi Async Iterator Helpers pružaju moćan i elegantan način za obradu tih tokova, nudeći funkcionalan i kompozabilan pristup manipulaciji podacima.

Što su asinkroni iteratori i asinkroni iterabili?

Prije nego što zaronimo u Async Iterator Helpers, razumijmo temeljne koncepte: asinkrone iteratore i asinkrone iterabile.

Asinkroni iterabil je objekt koji definira način asinkronog iteriranja preko svojih vrijednosti. To čini implementacijom metode @@asyncIterator, koja vraća asinkroni iterator.

Asinkroni iterator je objekt koji pruža metodu next(). Ova metoda vraća promise koji se rješava u objekt s dva svojstva:

Evo jednostavnog primjera:


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

const asyncIterable = generateSequence(5);

(async () => {
  for await (const value of asyncIterable) {
    console.log(value); // Izlaz: 1, 2, 3, 4, 5 (s kašnjenjem od 500ms između svakog)
  }
})();

U ovom primjeru, generateSequence je asinkrona generatorska funkcija koja asinkrono proizvodi niz brojeva. Petlja for await...of koristi se za konzumiranje vrijednosti iz asinkronog iterabila.

Uvod u Async Iterator Helpers

Async Iterator Helpers proširuju funkcionalnost asinkronih iteratora, pružajući skup metoda za transformaciju, filtriranje i manipulaciju asinkronim tokovima podataka. Omogućuju funkcionalan i kompozabilan stil programiranja, olakšavajući izgradnju složenih cjevovoda za obradu podataka.

Osnovni Async Iterator Helpers uključuju:

Istražimo svaki pomoćnik s primjerima.

map()

Pomoćnik map() transformira svaki element asinkronog iterabila pomoću zadane funkcije. Vraća novi asinkroni iterabil s transformiranim vrijednostima.


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); // Izlaz: 2, 4, 6, 8, 10 (s kašnjenjem od 100ms)
  }
})();

U ovom primjeru, map(x => x * 2) udvostručuje svaki broj u nizu.

filter()

Pomoćnik filter() odabire elemente iz asinkronog iterabila na temelju zadanog uvjeta (predikatne funkcije). Vraća novi asinkroni iterabil koji sadrži samo elemente koji zadovoljavaju uvjet.


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); // Izlaz: 2, 4, 6, 8, 10 (s kašnjenjem od 100ms)
  }
})();

U ovom primjeru, filter(x => x % 2 === 0) odabire samo parne brojeve iz niza.

take()

Pomoćnik take() vraća prvih N elemenata iz asinkronog iterabila. Vraća novi asinkroni iterabil koji sadrži samo navedeni broj elemenata.


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); // Izlaz: 1, 2, 3 (s kašnjenjem od 100ms)
  }
})();

U ovom primjeru, take(3) odabire prva tri broja iz niza.

drop()

Pomoćnik drop() preskače prvih N elemenata iz asinkronog iterabila i vraća ostatak. Vraća novi asinkroni iterabil koji sadrži preostale 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); // Izlaz: 3, 4, 5 (s kašnjenjem od 100ms)
  }
})();

U ovom primjeru, drop(2) preskače prva dva broja iz niza.

toArray()

Pomoćnik toArray() konzumira cijeli asinkroni iterabil i skuplja sve elemente u polje. Vraća promise koji se rješava u polje koje sadrži sve 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); // Izlaz: [1, 2, 3, 4, 5]
})();

U ovom primjeru, toArray() skuplja sve brojeve iz niza u polje.

forEach()

Pomoćnik forEach() izvršava zadanu funkciju jednom za svaki element u asinkronom iterabilu. On *ne* vraća novi asinkroni iterabil, već izvršava funkciju s nuspojavama. To može biti korisno za izvođenje operacija poput zapisivanja (logging) ili ažuriranja korisničkog sučelja.


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

some()

Pomoćnik some() testira prolazi li barem jedan element u asinkronom iterabilu test implementiran zadanom funkcijom. Vraća promise koji se rješava u booleovu vrijednost (true ako barem jedan element zadovoljava uvjet, inače 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); // Izlaz: Has even number: true
})();

every()

Pomoćnik every() testira prolaze li svi elementi u asinkronom iterabilu test implementiran zadanom funkcijom. Vraća promise koji se rješava u booleovu vrijednost (true ako svi elementi zadovoljavaju uvjet, inače 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); // Izlaz: Are all even: true
})();

find()

Pomoćnik find() vraća prvi element u asinkronom iterabilu koji zadovoljava zadanu testnu funkciju. Ako nijedna vrijednost ne zadovoljava testnu funkciju, vraća se undefined. Vraća promise koji se rješava u pronađeni element ili 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); // Izlaz: First even number: 2
})();

reduce()

Pomoćnik reduce() izvršava korisnički definiranu "reducer" povratnu funkciju na svakom elementu asinkronog iterabila, redom, prosljeđujući povratnu vrijednost iz izračuna na prethodnom elementu. Konačni rezultat izvršavanja reducera preko svih elemenata je jedna vrijednost. Vraća promise koji se rješava u konačnu akumuliranu vrijednost.


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

Praktični primjeri i slučajevi upotrebe

Async Iterator Helpers vrijedni su u različitim scenarijima. Istražimo neke praktične primjere:

1. Obrada podataka sa streaming API-ja

Zamislite da gradite nadzornu ploču za vizualizaciju podataka u stvarnom vremenu koja prima podatke sa streaming API-ja. API neprestano šalje ažuriranja, a vi trebate obraditi ta ažuriranja kako biste prikazali najnovije informacije.


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);
      // Pretpostavljajući da API šalje JSON objekte odvojene novim redovima
      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'; // Zamijenite sa svojim URL-om API-ja
const dataStream = fetchDataFromAPI(apiURL);

// Obradite tok podataka
(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);
    // Ažurirajte nadzornu ploču obrađenim podacima
  }
})();

U ovom primjeru, fetchDataFromAPI dohvaća podatke sa streaming API-ja, parsira JSON objekte i daje ih kao asinkroni iterabil. Pomoćnik filter odabire samo metrike, a pomoćnik map transformira podatke u željeni format prije ažuriranja nadzorne ploče.

2. Čitanje i obrada velikih datoteka

Pretpostavimo da trebate obraditi veliku CSV datoteku koja sadrži podatke o kupcima. Umjesto učitavanja cijele datoteke u memoriju, možete koristiti Async Iterator Helpers za obradu dio po dio.


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'; // Zamijenite putanjom do vaše datoteke
const lines = readLinesFromFile(filePath);

// Obradite retke
(async () => {
  for await (const customerData of lines.drop(1).map(line => line.split(',')).filter(data => data[2] === 'USA')) {
    console.log('Customer from USA:', customerData);
    // Obradite podatke o kupcima iz SAD-a
  }
})();

U ovom primjeru, readLinesFromFile čita datoteku redak po redak i daje svaki redak kao asinkroni iterabil. Pomoćnik drop(1) preskače zaglavlje, pomoćnik map dijeli redak u stupce, a pomoćnik filter odabire samo kupce iz SAD-a.

3. Rukovanje događajima u stvarnom vremenu

Async Iterator Helpers mogu se također koristiti za rukovanje događajima u stvarnom vremenu iz izvora poput WebSocketsa. Možete stvoriti asinkroni iterabil koji emitira događaje kako stižu, a zatim koristiti pomoćnike za obradu tih događaja.


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); // Riješite s null kada se veza zatvori
        }
      });

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

const websocketURL = 'wss://example.com/events'; // Zamijenite svojim WebSocket URL-om
const eventStream = createWebSocketStream(websocketURL);

// Obradite tok događaja
(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);
    // Obradite događaj prijave korisnika
  }
})();

U ovom primjeru, createWebSocketStream stvara asinkroni iterabil koji emitira događaje primljene s WebSocketa. Pomoćnik filter odabire samo događaje prijave korisnika, a pomoćnik map transformira podatke u željeni format.

Prednosti korištenja Async Iterator Helpers

Podrška u preglednicima i runtime okruženjima

Async Iterator Helpers su još uvijek relativno nova značajka u JavaScriptu. Krajem 2024. godine, nalaze se u 3. fazi TC39 procesa standardizacije, što znači da će vjerojatno biti standardizirani u bliskoj budućnosti. Međutim, još uvijek nisu nativno podržani u svim preglednicima i verzijama Node.js-a.

Podrška u preglednicima: Moderni preglednici poput Chromea, Firefoxa, Safarija i Edgea postupno dodaju podršku za Async Iterator Helpers. Možete provjeriti najnovije informacije o kompatibilnosti preglednika na web stranicama kao što je Can I use... da biste vidjeli koji preglednici podržavaju ovu značajku.

Podrška u Node.js-u: Novije verzije Node.js-a (v18 i novije) pružaju eksperimentalnu podršku za Async Iterator Helpers. Da biste ih koristili, možda ćete morati pokrenuti Node.js s zastavicom --experimental-async-iterator.

Polyfills: Ako trebate koristiti Async Iterator Helpers u okruženjima koja ih nativno ne podržavaju, možete koristiti polyfill. Polyfill je dio koda koji pruža nedostajuću funkcionalnost. Dostupno je nekoliko polyfill biblioteka za Async Iterator Helpers; popularna opcija je biblioteka core-js.

Implementacija prilagođenih asinkronih iteratora

Iako Async Iterator Helpers pružaju prikladan način za obradu postojećih asinkronih iterabila, ponekad ćete možda morati stvoriti vlastite prilagođene asinkrone iteratore. To vam omogućuje da rukujete podacima iz različitih izvora, kao što su baze podataka, API-ji ili datotečni sustavi, na streaming način.

Da biste stvorili prilagođeni asinkroni iterator, trebate implementirati metodu @@asyncIterator na objektu. Ova metoda treba vratiti objekt s metodom next(). Metoda next() treba vratiti promise koji se rješava u objekt sa svojstvima value i done.

Evo primjera prilagođenog asinkronog iteratora koji dohvaća podatke s paginiranog API-ja:


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'; // Zamijenite svojim URL-om API-ja
const paginatedData = fetchPaginatedData(apiBaseURL);

// Obradite paginirane podatke
(async () => {
  for await (const item of paginatedData) {
    console.log('Item:', item);
    // Obradite stavku
  }
})();

U ovom primjeru, fetchPaginatedData dohvaća podatke s paginiranog API-ja, dajući svaku stavku kako je dohvaćena. Asinkroni iterator rukuje logikom paginacije, olakšavajući konzumiranje podataka na streaming način.

Potencijalni izazovi i razmatranja

Iako Async Iterator Helpers nude brojne prednosti, važno je biti svjestan nekih potencijalnih izazova i razmatranja:

Najbolje prakse za korištenje Async Iterator Helpers

Da biste maksimalno iskoristili Async Iterator Helpers, razmotrite sljedeće najbolje prakse:

Napredne tehnike

Sastavljanje prilagođenih pomoćnika

Možete stvoriti vlastite prilagođene pomoćnike asinkronih iteratora sastavljanjem postojećih pomoćnika ili izgradnjom novih od nule. To vam omogućuje da prilagodite funkcionalnost svojim specifičnim potrebama i stvorite ponovno iskoristive komponente.


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

// Primjer upotrebe:
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);
  }
})();

Kombiniranje više asinkronih iterabila

Možete kombinirati više asinkronih iterabila u jedan asinkroni iterabil koristeći tehnike poput zip ili merge. To vam omogućuje da istovremeno obrađujete podatke iz više izvora.


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

// Primjer upotrebe:
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);
    }
})();

Zaključak

JavaScript Async Iterator Helpers pružaju moćan i elegantan način za obradu asinkronih tokova podataka. Nude funkcionalan i kompozabilan pristup manipulaciji podacima, olakšavajući izgradnju složenih cjevovoda za obradu podataka. Razumijevanjem temeljnih koncepata asinkronih iteratora i asinkronih iterabila te ovladavanjem različitim pomoćnim metodama, možete značajno poboljšati učinkovitost i održivost vašeg asinkronog JavaScript koda. Kako podrška u preglednicima i runtime okruženjima nastavlja rasti, Async Iterator Helpers postat će ključan alat za moderne JavaScript programere.