Iskoristite moć JS Async Generator pomoćnika za efikasno upravljanje tokovima. Otkrijte praktične primjere za izradu robusnih asinkronih aplikacija.
JavaScript Async Generator Pomoćnici: Ovladavanje Stvaranjem i Upravljanjem Tokovima
Asinkrono programiranje u JavaScriptu značajno je evoluiralo tijekom godina. Uvođenjem asinkronih generatora i asinkronih iteratora, programeri su dobili moćne alate za rukovanje tokovima asinkronih podataka. Sada, JavaScript Async Generator pomoćnici dodatno poboljšavaju te mogućnosti, pružajući jednostavniji i izražajniji način za stvaranje, transformaciju i upravljanje asinkronim tokovima podataka. Ovaj vodič istražuje osnove Async Generator pomoćnika, ulazi u njihove funkcionalnosti i demonstrira njihovu praktičnu primjenu s jasnim primjerima.
Razumijevanje Asinkronih Generatora i Iteratora
Prije nego što zaronimo u Async Generator pomoćnike, ključno je razumjeti temeljne koncepte asinkronih generatora i asinkronih iteratora.
Asinkroni Generatori
Asinkroni generator je funkcija koja se može pauzirati i nastaviti, asinkrono dajući vrijednosti. Omogućuje vam generiranje niza vrijednosti tijekom vremena, bez blokiranja glavne niti. Asinkroni generatori definiraju se pomoću sintakse async function*.
Primjer:
async function* generateSequence(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 500)); // Simulacija asinkrone operacije
yield i;
}
}
// Upotreba
const sequence = generateSequence(1, 5);
Asinkroni Iteratori
Asinkroni iterator je objekt koji pruža metodu next(), koja vraća obećanje (promise) koje se rješava objektom koji sadrži sljedeću vrijednost u nizu i svojstvo done koje označava je li niz iscrpljen. Asinkroni iteratori konzumiraju se pomoću for await...of petlji.
Primjer:
async function* generateSequence(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 500));
yield i;
}
}
async function consumeSequence() {
const sequence = generateSequence(1, 5);
for await (const value of sequence) {
console.log(value);
}
}
consumeSequence();
Uvod u Async Generator Pomoćnike
Async Generator pomoćnici su skup metoda koje proširuju funkcionalnost prototipova asinkronih generatora. Pružaju praktične načine za manipulaciju asinkronim tokovima podataka, čineći kod čitljivijim i lakšim za održavanje. Ovi pomoćnici rade lijeno (lazily), što znači da obrađuju podatke samo kada je to potrebno, što može poboljšati performanse.
Sljedeći Async Generator pomoćnici su obično dostupni (ovisno o JavaScript okruženju i polyfillovima):
mapfiltertakedropflatMapreducetoArrayforEach
Detaljno Istraživanje Async Generator Pomoćnika
1. `map()`
Pomoćnik map() transformira svaku vrijednost u asinkronom nizu primjenom zadane funkcije. Vraća novi asinkroni generator koji daje transformirane vrijednosti.
Sintaksa:
asyncGenerator.map(callback)
Primjer: Pretvaranje toka brojeva u njihove kvadrate.
async function* generateNumbers(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 200));
yield i;
}
}
async function processNumbers() {
const numbers = generateNumbers(1, 5);
const squares = numbers.map(async (num) => {
await new Promise(resolve => setTimeout(resolve, 100)); // Simulacija asinkrone operacije
return num * num;
});
for await (const square of squares) {
console.log(square);
}
}
processNumbers();
Primjer iz stvarnog svijeta: Zamislite dohvaćanje korisničkih podataka s više API-ja i potrebu za transformacijom podataka u dosljedan format. map() se može koristiti za asinkronu primjenu transformacijske funkcije na svaki korisnički objekt.
async function* fetchUsersFromMultipleAPIs(apiEndpoints) {
for (const endpoint of apiEndpoints) {
const response = await fetch(endpoint);
const data = await response.json();
for (const user of data) {
yield user;
}
}
}
async function processUsers() {
const apiEndpoints = [
'https://api.example.com/users1',
'https://api.example.com/users2'
];
const users = fetchUsersFromMultipleAPIs(apiEndpoints);
const normalizedUsers = users.map(async (user) => {
// Normalizacija formata korisničkih podataka
return {
id: user.userId || user.id,
name: user.fullName || user.name,
email: user.emailAddress || user.email
};
});
for await (const normalizedUser of normalizedUsers) {
console.log(normalizedUser);
}
}
2. `filter()`
Pomoćnik filter() stvara novi asinkroni generator koji daje samo one vrijednosti iz izvornog niza koje zadovoljavaju zadani uvjet. Omogućuje vam selektivno uključivanje vrijednosti u rezultirajući tok.
Sintaksa:
asyncGenerator.filter(callback)
Primjer: Filtriranje toka brojeva kako bi uključivao samo parne brojeve.
async function* generateNumbers(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 200));
yield i;
}
}
async function processNumbers() {
const numbers = generateNumbers(1, 10);
const evenNumbers = numbers.filter(async (num) => {
await new Promise(resolve => setTimeout(resolve, 100));
return num % 2 === 0;
});
for await (const evenNumber of evenNumbers) {
console.log(evenNumber);
}
}
processNumbers();
Primjer iz stvarnog svijeta: Obrada toka zapisa logova i filtriranje zapisa na temelju njihove razine ozbiljnosti. Na primjer, obrada samo grešaka i upozorenja.
async function* readLogFile(filePath) {
// Simulacija asinkronog čitanja log datoteke redak po redak
const logEntries = [
{ timestamp: '...', level: 'INFO', message: '...' },
{ timestamp: '...', level: 'ERROR', message: '...' },
{ timestamp: '...', level: 'WARNING', message: '...' },
{ timestamp: '...', level: 'INFO', message: '...' },
{ timestamp: '...', level: 'ERROR', message: '...' }
];
for (const entry of logEntries) {
await new Promise(resolve => setTimeout(resolve, 50));
yield entry;
}
}
async function processLogs() {
const logEntries = readLogFile('path/to/log/file.log');
const errorAndWarningLogs = logEntries.filter(async (entry) => {
return entry.level === 'ERROR' || entry.level === 'WARNING';
});
for await (const log of errorAndWarningLogs) {
console.log(log);
}
}
3. `take()`
Pomoćnik take() stvara novi asinkroni generator koji daje samo prvih n vrijednosti iz izvornog niza. Koristan je za ograničavanje broja stavki koje se obrađuju iz potencijalno beskonačnog ili vrlo velikog toka.
Sintaksa:
asyncGenerator.take(n)
Primjer: Uzimanje prva 3 broja iz toka brojeva.
async function* generateNumbers(start) {
let i = start;
while (true) {
await new Promise(resolve => setTimeout(resolve, 200));
yield i++;
}
}
async function processNumbers() {
const numbers = generateNumbers(1);
const firstThree = numbers.take(3);
for await (const num of firstThree) {
console.log(num);
}
}
processNumbers();
Primjer iz stvarnog svijeta: Prikazivanje prvih 5 rezultata pretraživanja iz asinkronog API-ja za pretraživanje.
async function* search(query) {
// Simulacija dohvaćanja rezultata pretraživanja s API-ja
const results = [
{ title: 'Result 1', url: '...' },
{ title: 'Result 2', url: '...' },
{ title: 'Result 3', url: '...' },
{ title: 'Result 4', url: '...' },
{ title: 'Result 5', url: '...' },
{ title: 'Result 6', url: '...' }
];
for (const result of results) {
await new Promise(resolve => setTimeout(resolve, 100));
yield result;
}
}
async function displayTopSearchResults(query) {
const searchResults = search(query);
const top5Results = searchResults.take(5);
for await (const result of top5Results) {
console.log(result);
}
}
4. `drop()`
Pomoćnik drop() stvara novi asinkroni generator koji preskače prvih n vrijednosti iz izvornog niza i daje preostale vrijednosti. To je suprotno od take() i korisno je za ignoriranje početnih dijelova toka.
Sintaksa:
asyncGenerator.drop(n)
Primjer: Odbacivanje prva 2 broja iz toka brojeva.
async function* generateNumbers(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 200));
yield i;
}
}
async function processNumbers() {
const numbers = generateNumbers(1, 5);
const remainingNumbers = numbers.drop(2);
for await (const num of remainingNumbers) {
console.log(num);
}
}
processNumbers();
Primjer iz stvarnog svijeta: Paginacija kroz veliki skup podataka dohvaćen s API-ja, preskačući već prikazane rezultate.
async function* fetchData(url, pageSize, pageNumber) {
const offset = (pageNumber - 1) * pageSize;
// Simulacija dohvaćanja podataka s pomakom
const data = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' },
{ id: 4, name: 'Item 4' },
{ id: 5, name: 'Item 5' },
{ id: 6, name: 'Item 6' },
{ id: 7, name: 'Item 7' },
{ id: 8, name: 'Item 8' }
];
const pageData = data.slice(offset, offset + pageSize);
for (const item of pageData) {
await new Promise(resolve => setTimeout(resolve, 100));
yield item;
}
}
async function displayPage(pageNumber) {
const pageSize = 3;
const allData = fetchData('api/data', pageSize, pageNumber);
const page = allData.drop((pageNumber - 1) * pageSize); // preskoči stavke s prethodnih stranica
const results = page.take(pageSize);
for await (const item of results) {
console.log(item);
}
}
// Primjer upotrebe
displayPage(2);
5. `flatMap()`
Pomoćnik flatMap() transformira svaku vrijednost u asinkronom nizu primjenom funkcije koja vraća asinkroni iterator (Async Iterable). Zatim izravnava rezultirajući asinkroni iterator u jedan asinkroni generator. Ovo je korisno za transformaciju svake vrijednosti u tok vrijednosti i zatim kombiniranje tih tokova.
Sintaksa:
asyncGenerator.flatMap(callback)
Primjer: Pretvaranje toka rečenica u tok riječi.
async function* generateSentences() {
const sentences = [
'This is the first sentence.',
'This is the second sentence.',
'This is the third sentence.'
];
for (const sentence of sentences) {
await new Promise(resolve => setTimeout(resolve, 200));
yield sentence;
}
}
async function* stringToWords(sentence) {
const words = sentence.split(' ');
for (const word of words) {
await new Promise(resolve => setTimeout(resolve, 50));
yield word;
}
}
async function processSentences() {
const sentences = generateSentences();
const words = sentences.flatMap(async (sentence) => {
return stringToWords(sentence);
});
for await (const word of words) {
console.log(word);
}
}
processSentences();
Primjer iz stvarnog svijeta: Dohvaćanje komentara za više blog postova i njihovo kombiniranje u jedan tok za obradu.
async function* fetchBlogPostIds() {
const blogPostIds = [1, 2, 3]; // Simulacija dohvaćanja ID-ova blog postova s API-ja
for (const id of blogPostIds) {
await new Promise(resolve => setTimeout(resolve, 100));
yield id;
}
}
async function* fetchCommentsForPost(postId) {
// Simulacija dohvaćanja komentara za blog post s API-ja
const comments = [
{ postId: postId, text: `Comment 1 for post ${postId}` },
{ postId: postId, text: `Comment 2 for post ${postId}` }
];
for (const comment of comments) {
await new Promise(resolve => setTimeout(resolve, 50));
yield comment;
}
}
async function processComments() {
const postIds = fetchBlogPostIds();
const allComments = postIds.flatMap(async (postId) => {
return fetchCommentsForPost(postId);
});
for await (const comment of allComments) {
console.log(comment);
}
}
6. `reduce()`
Pomoćnik reduce() primjenjuje funkciju na akumulator i svaku vrijednost asinkronog generatora (s lijeva na desno) kako bi ga sveo na jednu vrijednost. Ovo je korisno za agregiranje podataka iz asinkronog toka.
Sintaksa:
asyncGenerator.reduce(callback, initialValue)
Primjer: Izračunavanje zbroja brojeva u toku.
async function* generateNumbers(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 200));
yield i;
}
}
async function processNumbers() {
const numbers = generateNumbers(1, 5);
const sum = await numbers.reduce(async (accumulator, num) => {
await new Promise(resolve => setTimeout(resolve, 100));
return accumulator + num;
}, 0);
console.log('Sum:', sum);
}
processNumbers();
Primjer iz stvarnog svijeta: Izračunavanje prosječnog vremena odziva niza API poziva.
async function* fetchResponseTimes(apiEndpoints) {
for (const endpoint of apiEndpoints) {
const startTime = Date.now();
try {
await fetch(endpoint);
const endTime = Date.now();
const responseTime = endTime - startTime;
await new Promise(resolve => setTimeout(resolve, 50));
yield responseTime;
} catch (error) {
console.error(`Error fetching ${endpoint}: ${error}`);
yield 0; // Ili rukujte greškom na odgovarajući način
}
}
}
async function calculateAverageResponseTime() {
const apiEndpoints = [
'https://api.example.com/endpoint1',
'https://api.example.com/endpoint2',
'https://api.example.com/endpoint3'
];
const responseTimes = fetchResponseTimes(apiEndpoints);
let count = 0;
const sum = await responseTimes.reduce(async (accumulator, time) => {
count++;
return accumulator + time;
}, 0);
const average = count > 0 ? sum / count : 0;
console.log(`Average response time: ${average} ms`);
}
7. `toArray()`
Pomoćnik toArray() konzumira asinkroni generator i vraća obećanje (promise) koje se rješava nizom koji sadrži sve vrijednosti koje je generator dao. Ovo je korisno kada trebate prikupiti sve vrijednosti iz toka u jedan niz za daljnju obradu.
Sintaksa:
asyncGenerator.toArray()
Primjer: Prikupljanje brojeva iz toka u niz.
async function* generateNumbers(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 200));
yield i;
}
}
async function processNumbers() {
const numbers = generateNumbers(1, 5);
const numberArray = await numbers.toArray();
console.log('Number Array:', numberArray);
}
processNumbers();
Primjer iz stvarnog svijeta: Prikupljanje svih stavki s paginiranog API-ja u jedan niz za filtriranje ili sortiranje na strani klijenta.
async function* fetchAllItems(apiEndpoint) {
let pageNumber = 1;
const pageSize = 100; // Prilagodite ovisno o ograničenjima paginacije API-ja
while (true) {
const url = `${apiEndpoint}?page=${pageNumber}&pageSize=${pageSize}`;
const response = await fetch(url);
const data = await response.json();
if (!data || data.length === 0) {
break; // Nema više podataka
}
for (const item of data) {
await new Promise(resolve => setTimeout(resolve, 50));
yield item;
}
pageNumber++;
}
}
async function processAllItems() {
const apiEndpoint = 'https://api.example.com/items';
const allItems = fetchAllItems(apiEndpoint);
const itemsArray = await allItems.toArray();
console.log(`Fetched ${itemsArray.length} items.`);
// Daljnja obrada može se izvršiti na `itemsArray`
}
8. `forEach()`
Pomoćnik forEach() izvršava zadanu funkciju jednom za svaku vrijednost u asinkronom generatoru. Za razliku od drugih pomoćnika, forEach() ne vraća novi asinkroni generator; koristi se za izvođenje sporednih efekata na svakoj vrijednosti.
Sintaksa:
asyncGenerator.forEach(callback)
Primjer: Ispisivanje svakog broja iz toka u konzolu.
async function* generateNumbers(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 200));
yield i;
}
}
async function processNumbers() {
const numbers = generateNumbers(1, 5);
await numbers.forEach(async (num) => {
await new Promise(resolve => setTimeout(resolve, 100));
console.log('Number:', num);
});
}
processNumbers();
Primjer iz stvarnog svijeta: Slanje ažuriranja u stvarnom vremenu korisničkom sučelju kako se podaci obrađuju iz toka.
async function* fetchRealTimeData(dataSource) {
//Simulacija dohvaćanja podataka u stvarnom vremenu (npr. cijene dionica).
const dataStream = [
{ timestamp: new Date(), price: 100 },
{ timestamp: new Date(), price: 101 },
{ timestamp: new Date(), price: 102 }
];
for (const dataPoint of dataStream) {
await new Promise(resolve => setTimeout(resolve, 500));
yield dataPoint;
}
}
async function updateUI() {
const realTimeData = fetchRealTimeData('stock-api');
await realTimeData.forEach(async (data) => {
//Simulacija ažuriranja korisničkog sučelja
await new Promise(resolve => setTimeout(resolve, 100));
console.log(`Updating UI with data: ${JSON.stringify(data)}`);
// Kod za stvarno ažuriranje korisničkog sučelja bi išao ovdje.
});
}
Kombiniranje Async Generator Pomoćnika za Složene Podatkovne Cjevovode
Prava snaga Async Generator pomoćnika dolazi iz njihove mogućnosti da se lančano povezuju kako bi se stvorili složeni podatkovni cjevovodi. To vam omogućuje izvođenje višestrukih transformacija i operacija na asinkronom toku na sažet i čitljiv način.
Primjer: Filtriranje toka brojeva kako bi uključivao samo parne brojeve, zatim njihovo kvadriranje i na kraju uzimanje prva 3 rezultata.
async function* generateNumbers(start) {
let i = start;
while (true) {
await new Promise(resolve => setTimeout(resolve, 100));
yield i++;
}
}
async function processNumbers() {
const numbers = generateNumbers(1);
const processedNumbers = numbers
.filter(async (num) => num % 2 === 0)
.map(async (num) => num * num)
.take(3);
for await (const num of processedNumbers) {
console.log(num);
}
}
processNumbers();
Primjer iz stvarnog svijeta: Dohvaćanje korisničkih podataka, filtriranje korisnika na temelju njihove lokacije, transformiranje njihovih podataka kako bi uključivali samo relevantna polja, a zatim prikazivanje prvih 10 korisnika na karti.
async function* fetchUsers() {
// Simulacija dohvaćanja korisnika iz baze podataka ili API-ja
const users = [
{ id: 1, name: 'John Doe', location: 'New York', email: 'john.doe@example.com' },
{ id: 2, name: 'Jane Smith', location: 'London', email: 'jane.smith@example.com' },
{ id: 3, name: 'Ken Tan', location: 'Singapore', email: 'ken.tan@example.com' },
{ id: 4, name: 'Alice Jones', location: 'New York', email: 'alice.jones@example.com' },
{ id: 5, name: 'Bob Williams', location: 'London', email: 'bob.williams@example.com' },
{ id: 6, name: 'Siti Rahman', location: 'Singapore', email: 'siti.rahman@example.com' },
{ id: 7, name: 'Ahmed Khan', location: 'Dubai', email: 'ahmed.khan@example.com' },
{ id: 8, name: 'Maria Garcia', location: 'Madrid', email: 'maria.garcia@example.com' },
{ id: 9, name: 'Li Wei', location: 'Shanghai', email: 'li.wei@example.com' },
{ id: 10, name: 'Hans Müller', location: 'Berlin', email: 'hans.muller@example.com' },
{ id: 11, name: 'Emily Chen', location: 'Sydney', email: 'emily.chen@example.com' }
];
for (const user of users) {
await new Promise(resolve => setTimeout(resolve, 50));
yield user;
}
}
async function displayUsersOnMap(location, maxUsers) {
const users = fetchUsers();
const usersForMap = users
.filter(async (user) => user.location === location)
.map(async (user) => ({
id: user.id,
name: user.name,
location: user.location
}))
.take(maxUsers);
console.log(`Displaying up to ${maxUsers} users from ${location} on the map:`);
for await (const user of usersForMap) {
console.log(user);
}
}
// Primjeri upotrebe:
displayUsersOnMap('New York', 2);
displayUsersOnMap('London', 5);
Polyfillovi i Podrška Preglednika
Podrška za Async Generator pomoćnike može varirati ovisno o JavaScript okruženju. Ako trebate podržati starije preglednike ili okruženja, možda ćete morati koristiti polyfillove. Polyfill pruža nedostajuću funkcionalnost implementirajući je u JavaScriptu. Dostupno je nekoliko polyfill biblioteka za Async Generator pomoćnike, kao što je core-js.
Primjer korištenja core-js:
// Uvezite potrebne polyfillove
require('core-js/features/async-iterator/map');
require('core-js/features/async-iterator/filter');
// ... uvezite druge potrebne pomoćnike
Rukovanje Pogreškama
Kada radite s asinkronim operacijama, ključno je pravilno rukovati pogreškama. S Async Generator pomoćnicima, rukovanje pogreškama može se obaviti pomoću try...catch blokova unutar asinkronih funkcija koje se koriste u pomoćnicima.
Primjer: Rukovanje pogreškama prilikom dohvaćanja podataka unutar map() operacije.
async function* fetchData(urls) {
for (const url of urls) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
yield data;
} catch (error) {
console.error(`Error fetching data from ${url}: ${error}`);
yield null; // Ili rukujte pogreškom na odgovarajući način, npr. davanjem objekta pogreške
}
}
}
async function processData() {
const urls = [
'https://api.example.com/data1',
'https://api.example.com/data2',
'https://api.example.com/data3'
];
const dataStream = fetchData(urls);
const processedData = dataStream.map(async (data) => {
if (data === null) {
return null; // Proslijedi pogrešku
}
// Obradi podatke
return data;
});
for await (const item of processedData) {
if (item === null) {
console.log('Skipping item due to error');
continue;
}
console.log('Processed Item:', item);
}
}
processData();
Najbolje Prakse i Preporuke
- Lijena Evaluacija: Async Generator pomoćnici se evaluiraju lijeno, što znači da obrađuju podatke samo kada se to zatraži. To može poboljšati performanse, posebno kod rada s velikim skupovima podataka.
- Rukovanje Pogreškama: Uvijek pravilno rukujte pogreškama unutar asinkronih funkcija koje se koriste u pomoćnicima.
- Polyfillovi: Koristite polyfillove kada je potrebno za podršku starijim preglednicima ili okruženjima.
- Čitljivost: Koristite opisna imena varijabli i komentare kako biste svoj kod učinili čitljivijim i lakšim za održavanje.
- Performanse: Budite svjesni implikacija na performanse lančanog povezivanja više pomoćnika. Iako lijenost pomaže, pretjerano lančano povezivanje i dalje može uvesti dodatno opterećenje.
Zaključak
JavaScript Async Generator pomoćnici pružaju moćan i elegantan način za stvaranje, transformaciju i upravljanje asinkronim tokovima podataka. Korištenjem ovih pomoćnika, programeri mogu pisati sažetiji, čitljiviji i održiviji kod za rukovanje složenim asinkronim operacijama. Razumijevanje osnova asinkronih generatora i iteratora, zajedno s funkcionalnostima svakog pomoćnika, ključno je za učinkovito korištenje ovih alata u stvarnim aplikacijama. Bilo da gradite podatkovne cjevovode, obrađujete podatke u stvarnom vremenu ili rukujete asinkronim odgovorima API-ja, Async Generator pomoćnici mogu značajno pojednostaviti vaš kod i poboljšati njegovu ukupnu učinkovitost.