Istražite Web Streams API za učinkovitu obradu podataka u JavaScriptu. Naučite kako stvarati, transformirati i konzumirati streamove za poboljšane performanse i upravljanje memorijom.
Web Streams API: Učinkoviti cjevovodi za obradu podataka u JavaScriptu
Web Streams API pruža moćan mehanizam za rukovanje streaming podacima u JavaScriptu, omogućujući učinkovite i responzivne web aplikacije. Umjesto da se cijeli skupovi podataka učitavaju u memoriju odjednom, streamovi vam omogućuju inkrementalnu obradu podataka, smanjujući potrošnju memorije i poboljšavajući performanse. To je posebno korisno kod rada s velikim datotekama, mrežnim zahtjevima ili podatkovnim feedovima u stvarnom vremenu.
Što su Web Streamovi?
U svojoj suštini, Web Streams API nudi tri glavne vrste streamova:
- ReadableStream: Predstavlja izvor podataka, poput datoteke, mrežne veze ili generiranih podataka.
- WritableStream: Predstavlja odredište za podatke, poput datoteke, mrežne veze ili baze podataka.
- TransformStream: Predstavlja cjevovod za transformaciju između ReadableStreama i WritableStreama. Može mijenjati ili obrađivati podatke dok prolaze kroz stream.
Ove vrste streamova rade zajedno kako bi stvorile učinkovite cjevovode za obradu podataka. Podaci teku iz ReadableStreama, kroz opcionalne TransformStreamove, i na kraju do WritableStreama.
Ključni pojmovi i terminologija
- Chunks (dijelovi): Podaci se obrađuju u diskretnim jedinicama koje se nazivaju chunkovi. Chunk može biti bilo koja JavaScript vrijednost, poput stringa, broja ili objekta.
- Controllers (kontroleri): Svaka vrsta streama ima odgovarajući objekt kontrolera koji pruža metode za upravljanje streamom. Na primjer, ReadableStreamController vam omogućuje da stavite podatke u red čekanja (enqueue) streama, dok WritableStreamController omogućuje rukovanje dolaznim chunkovima.
- Pipes (cjevovodi): Streamovi se mogu povezati pomoću metoda
pipeTo()
ipipeThrough()
.pipeTo()
povezuje ReadableStream s WritableStreamom, dokpipeThrough()
povezuje ReadableStream s TransformStreamom, a zatim s WritableStreamom. - Backpressure (povratni pritisak): Mehanizam koji omogućuje potrošaču da signalizira proizvođaču da nije spreman primiti više podataka. To sprječava preopterećenje potrošača i osigurava da se podaci obrađuju održivom brzinom.
Kreiranje ReadableStreama
ReadableStream možete kreirati pomoću konstruktora ReadableStream()
. Konstruktor kao argument prima objekt koji može definirati nekoliko metoda za kontrolu ponašanja streama. Najvažnije od njih su metoda start()
, koja se poziva kada se stream kreira, i metoda pull()
, koja se poziva kada stream treba više podataka.
Evo primjera kreiranja ReadableStreama koji generira niz brojeva:
const readableStream = new ReadableStream({
start(controller) {
let counter = 0;
function push() {
if (counter >= 10) {
controller.close();
return;
}
controller.enqueue(counter++);
setTimeout(push, 100);
}
push();
},
});
U ovom primjeru, metoda start()
inicijalizira brojač i definira funkciju push()
koja stavlja broj u red čekanja (enqueue) streama, a zatim se ponovno poziva nakon kratke odgode. Metoda controller.close()
poziva se kada brojač dosegne 10, signalizirajući da je stream završen.
Konzumiranje ReadableStreama
Za konzumiranje podataka iz ReadableStreama možete koristiti ReadableStreamDefaultReader
. Čitač (reader) pruža metode za čitanje chunkova iz streama. Najvažnija od njih je metoda read()
, koja vraća promise koji se razrješava s objektom koji sadrži chunk podataka i zastavicu (flag) koja pokazuje je li stream završen.
Evo primjera konzumiranja podataka iz ReadableStreama kreiranog u prethodnom primjeru:
const reader = readableStream.getReader();
async function read() {
const { done, value } = await reader.read();
if (done) {
console.log('Stream complete');
return;
}
console.log('Received:', value);
read();
}
read();
U ovom primjeru, funkcija read()
čita chunk iz streama, ispisuje ga u konzolu, a zatim se ponovno poziva dok stream nije završen.
Kreiranje WritableStreama
WritableStream možete kreirati pomoću konstruktora WritableStream()
. Konstruktor kao argument prima objekt koji može definirati nekoliko metoda za kontrolu ponašanja streama. Najvažnije od njih su metoda write()
, koja se poziva kada je chunk podataka spreman za pisanje, metoda close()
, koja se poziva kada se stream zatvori, i metoda abort()
, koja se poziva kada se stream prekine.
Evo primjera kreiranja WritableStreama koji svaki chunk podataka ispisuje u konzolu:
const writableStream = new WritableStream({
write(chunk) {
console.log('Writing:', chunk);
return Promise.resolve(); // Označava uspjeh
},
close() {
console.log('Stream closed');
},
abort(err) {
console.error('Stream aborted:', err);
},
});
U ovom primjeru, metoda write()
ispisuje chunk u konzolu i vraća promise koji se razrješava kada je chunk uspješno zapisan. Metode close()
i abort()
ispisuju poruke u konzolu kada se stream zatvori, odnosno prekine.
Pisanje u WritableStream
Za pisanje podataka u WritableStream možete koristiti WritableStreamDefaultWriter
. Pisač (writer) pruža metode za pisanje chunkova u stream. Najvažnija od njih je metoda write()
, koja kao argument uzima chunk podataka i vraća promise koji se razrješava kada je chunk uspješno zapisan.
Evo primjera pisanja podataka u WritableStream kreiran u prethodnom primjeru:
const writer = writableStream.getWriter();
async function writeData() {
await writer.write('Hello, world!');
await writer.close();
}
writeData();
U ovom primjeru, funkcija writeData()
zapisuje string "Hello, world!" u stream, a zatim zatvara stream.
Kreiranje TransformStreama
TransformStream možete kreirati pomoću konstruktora TransformStream()
. Konstruktor kao argument prima objekt koji može definirati nekoliko metoda za kontrolu ponašanja streama. Najvažnije od njih su metoda transform()
, koja se poziva kada je chunk podataka spreman za transformaciju, i metoda flush()
, koja se poziva kada se stream zatvori.
Evo primjera kreiranja TransformStreama koji svaki chunk podataka pretvara u velika slova:
const transformStream = new TransformStream({
transform(chunk, controller) {
controller.enqueue(chunk.toUpperCase());
},
flush(controller) {
// Opcionalno: Izvršite bilo koje završne operacije prilikom zatvaranja streama
},
});
U ovom primjeru, metoda transform()
pretvara chunk u velika slova i stavlja ga u red čekanja kontrolera. Metoda flush()
poziva se kada se stream zatvara i može se koristiti za izvođenje bilo kakvih završnih operacija.
Korištenje TransformStreamova u cjevovodima
TransformStreamovi su najkorisniji kada su povezani u lanac kako bi stvorili cjevovode za obradu podataka. Možete koristiti metodu pipeThrough()
za povezivanje ReadableStreama s TransformStreamom, a zatim s WritableStreamom.
Evo primjera kreiranja cjevovoda koji čita podatke iz ReadableStreama, pretvara ih u velika slova pomoću TransformStreama, a zatim ih zapisuje u WritableStream:
const readableStream = new ReadableStream({
start(controller) {
controller.enqueue('hello');
controller.enqueue('world');
controller.close();
},
});
const transformStream = new TransformStream({
transform(chunk, controller) {
controller.enqueue(chunk.toUpperCase());
},
});
const writableStream = new WritableStream({
write(chunk) {
console.log('Writing:', chunk);
return Promise.resolve();
},
});
readableStream.pipeThrough(transformStream).pipeTo(writableStream);
U ovom primjeru, metoda pipeThrough()
povezuje readableStream
s transformStreamom
, a zatim metoda pipeTo()
povezuje transformStream
s writableStreamom
. Podaci teku iz ReadableStreama, kroz TransformStream (gdje se pretvaraju u velika slova), a zatim u WritableStream (gdje se ispisuju u konzolu).
Povratni pritisak (Backpressure)
Povratni pritisak (Backpressure) je ključan mehanizam u Web Streamovima koji sprječava da brzi proizvođač preoptereti sporog potrošača. Kada potrošač ne može pratiti brzinu kojom se podaci proizvode, može signalizirati proizvođaču da uspori. To se postiže putem kontrolera streama i objekata čitača/pisača.
Kada je interni red čekanja ReadableStreama pun, metoda pull()
neće biti pozvana dok se u redu ne oslobodi prostor. Slično tome, metoda write()
WritableStreama može vratiti promise koji se razrješava tek kada je stream spreman prihvatiti više podataka.
Pravilnim rukovanjem povratnim pritiskom možete osigurati da su vaši cjevovodi za obradu podataka robusni i učinkoviti, čak i kada se radi s promjenjivim brzinama podataka.
Slučajevi upotrebe i primjeri
1. Obrada velikih datoteka
Web Streams API je idealan za obradu velikih datoteka bez da ih se u potpunosti učitava u memoriju. Možete čitati datoteku u chunkovima, obraditi svaki chunk i zapisati rezultate u drugu datoteku ili stream.
async function processFile(inputFile, outputFile) {
const readableStream = fs.createReadStream(inputFile).pipeThrough(new TextDecoderStream());
const writableStream = fs.createWriteStream(outputFile).pipeThrough(new TextEncoderStream());
const transformStream = new TransformStream({
transform(chunk, controller) {
// Primjer: Pretvorite svaki redak u velika slova
const lines = chunk.split('\n');
lines.forEach(line => controller.enqueue(line.toUpperCase() + '\n'));
}
});
await readableStream.pipeThrough(transformStream).pipeTo(writableStream);
console.log('Obrada datoteke dovršena!');
}
// Primjer upotrebe (zahtijeva Node.js)
// const fs = require('fs');
// processFile('input.txt', 'output.txt');
2. Rukovanje mrežnim zahtjevima
Možete koristiti Web Streams API za obradu podataka primljenih iz mrežnih zahtjeva, kao što su API odgovori ili događaji poslani od strane poslužitelja (server-sent events). To vam omogućuje da počnete obrađivati podatke čim stignu, umjesto da čekate preuzimanje cijelog odgovora.
async function fetchAndProcessData(url) {
const response = await fetch(url);
const reader = response.body.getReader();
const decoder = new TextDecoder();
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
const text = decoder.decode(value);
// Obradite primljene podatke
console.log('Primljeno:', text);
}
} catch (error) {
console.error('Greška pri čitanju iz streama:', error);
} finally {
reader.releaseLock();
}
}
// Primjer upotrebe
// fetchAndProcessData('https://example.com/api/data');
3. Podatkovni feedovi u stvarnom vremenu
Web Streamovi su također prikladni za rukovanje podatkovnim feedovima u stvarnom vremenu, kao što su cijene dionica ili očitanja senzora. Možete povezati ReadableStream s izvorom podataka i obrađivati dolazne podatke kako pristižu.
// Primjer: Simulacija podatkovnog feeda u stvarnom vremenu
const readableStream = new ReadableStream({
start(controller) {
let intervalId = setInterval(() => {
const data = Math.random(); // Simuliraj očitanje senzora
controller.enqueue(`Data: ${data.toFixed(2)}`);
}, 1000);
this.cancel = () => {
clearInterval(intervalId);
controller.close();
};
},
cancel() {
this.cancel();
}
});
const reader = readableStream.getReader();
async function readStream() {
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
console.log('Stream zatvoren.');
break;
}
console.log('Primljeno:', value);
}
} catch (error) {
console.error('Greška pri čitanju iz streama:', error);
} finally {
reader.releaseLock();
}
}
readStream();
// Zaustavi stream nakon 10 sekundi
setTimeout(() => {readableStream.cancel()}, 10000);
Prednosti korištenja Web Streams API-ja
- Poboljšane performanse: Obrađujte podatke inkrementalno, smanjujući potrošnju memorije i poboljšavajući responzivnost.
- Poboljšano upravljanje memorijom: Izbjegavajte učitavanje cijelih skupova podataka u memoriju, što je posebno korisno za velike datoteke ili mrežne streamove.
- Bolje korisničko iskustvo: Počnite obrađivati i prikazivati podatke ranije, pružajući interaktivnije i responzivnije korisničko iskustvo.
- Pojednostavljena obrada podataka: Stvarajte modularne i višekratno iskoristive cjevovode za obradu podataka pomoću TransformStreamova.
- Podrška za povratni pritisak: Rukujte promjenjivim brzinama podataka i spriječite preopterećenje potrošača.
Razmatranja i najbolje prakse
- Rukovanje greškama: Implementirajte robusno rukovanje greškama kako biste elegantno obradili greške streama i spriječili neočekivano ponašanje aplikacije.
- Upravljanje resursima: Pravilno oslobodite resurse kada streamovi više nisu potrebni kako biste izbjegli curenje memorije. Koristite
reader.releaseLock()
i osigurajte da su streamovi zatvoreni ili prekinuti kada je to prikladno. - Kodiranje i dekodiranje: Koristite
TextEncoderStream
iTextDecoderStream
za rukovanje tekstualnim podacima kako biste osigurali ispravno kodiranje znakova. - Kompatibilnost preglednika: Provjerite kompatibilnost preglednika prije korištenja Web Streams API-ja i razmislite o korištenju polyfillova za starije preglednike.
- Testiranje: Temeljito testirajte svoje cjevovode za obradu podataka kako biste osigurali da ispravno funkcioniraju u različitim uvjetima.
Zaključak
Web Streams API pruža moćan i učinkovit način za rukovanje streaming podacima u JavaScriptu. Razumijevanjem ključnih koncepata i korištenjem različitih vrsta streamova, možete stvarati robusne i responzivne web aplikacije koje s lakoćom mogu rukovati velikim datotekama, mrežnim zahtjevima i podatkovnim feedovima u stvarnom vremenu. Implementacija povratnog pritiska i praćenje najboljih praksi za rukovanje greškama i upravljanje resursima osigurat će da su vaši cjevovodi za obradu podataka pouzdani i performantni. Kako se web aplikacije nastavljaju razvijati i rukovati sve složenijim podacima, Web Streams API postat će neophodan alat za programere diljem svijeta.