Mundarija (27)
- Bu darsda
- 1. Nega bu kerak?
- 2. for await bilan oqimni o'qish
- 2.1 Oqim — asinxron iterable
- 2.2 break oqimni yopadi
- 2.3 Xato try/catch ga tushadi
- 3. Qatorma-qator: readline va for await
- 3.1 Qo'shtirnoqli CSV
- 4. Web Streams Node'da
- 4.1 Ikki dunyo
- 4.2 toWeb va fromWeb
- 4.3 TransformStream va CompressionStream
- 5. compose, .map(), .filter()
- 6. Qaysi o'qish usuli qachon
- 7. Ko'p uchraydigan xatolar
- 7.1 Bitta oqimni ikki marta o'qish
- 7.2 crlfDelay ni unutish
- 7.3 Web Stream'ni .pipe() ga berish
- 7.4 Experimental API'ga tayanish
- 8. Mashqlar
- 1-mashq (oson): Birinchi USD
- 2-mashq (o'rta): Har valyuta bo'yicha o'rtacha
- 3-mashq (qiyin): Javobni siqib saqlash
- 4-mashq: CLI qadami — tahlilQil
- 9. Real ishda
- Xulosa
- Manbalar
Node.js stream va for await: async iteratorlar va Web Streams
Qisqacha: Node'dagi har bir Readable oqim — asinxron iterable: uni
for await (const bolak of oqim)bilan o'qiysiz. Sikl bo'lakni bittalab tortadi (backpressure o'z-o'zidan),breakoqimni yopadi, oqim xatosi esatry/catchga tushadi. Node brauzerdagi Web Streams'ni ham biladi:Readable.toWebvaReadable.fromWebikki dunyo orasida ko'prik.
Bu darsda
- Node oqimini
for awaitbilan o'qiysiz vabreak, xato holatlarini bilasiz. - Faylni
readlinebilan qatorma-qator o'qib, statistika hisoblaysiz. - Node oqimini Web Stream'ga va aksincha o'gira olasiz.
TransformStream,TextDecoderStream,CompressionStreamni Node'da ishlatasiz.compose,.map(),.filter()nima ekanini va nega ehtiyot kerakligini bilasiz.
Oldin bilishingiz kerak: O'z Readable/Transform stream'ingizni yozish, Asinxron iteratsiya, Streams API, readline va terminal.
1. Nega bu kerak?
Oldingi darsda oqimlarni on("data"), pipeline va generatorlar bilan o'qidik. Har usulning o'z qoidalari bor: data hodisasida backpressure'ni o'zingiz boshqarasiz, pipeline uchun qabul qiluvchi oqim kerak. Ko'pincha esa sizga oddiy narsa kerak: "faylning har qatorini ol, hisobla, oxirida natijani ber".
valyuta tahlil eur.csv.gz buyrug'i aynan shunday. U fayldagi kurslarni o'qib, har valyuta uchun eng kichik, eng katta va o'rtacha kursni chiqaradi. Bunday ishni oddiy sikl bilan yozish eng tushunarli. for await buni oqimlar uchun ham mumkin qiladi.
Ikkinchi muammo — ikki xil oqim. fetch javobining tanasi — Web Stream (Streams API darsidagi ReadableStream). fs va zlib esa Node oqimlarini beradi. CBU'dan kelgan javobni siqib faylga yozmoqchi bo'lsangiz, ikkalasini ulash kerak.
Hayotiy o'xshatish: ikki mamlakat temir yo'li — izlarining kengligi har xil. Poyezd chegarada to'xtab, g'ildiraklarini almashtiradi va yo'lida davom etadi. Readable.toWeb va Readable.fromWeb — shu almashtirish stansiyasi. Yuk (ma'lumot) o'sha-o'sha, faqat g'ildirak boshqa.
2. for await bilan oqimni o'qish
2.1 Oqim — asinxron iterable
Asinxron iteratsiya darsida Symbol.asyncIterator protokolini ko'rgansiz. Node'dagi har Readable uni amalga oshiradi. Demak for await to'g'ridan-to'g'ri ishlaydi:
import { Readable } from "node:stream";
const kunlar = Readable.from(["2026-10-21", "2026-10-22"]);
for await (const kun of kunlar) console.log("kun:", kun);
console.log("hammasi o'qildi");Konsolda:
kun: 2026-10-21
kun: 2026-10-22
hammasi o'qildiSikl har aylanishda bitta bo'lakni so'raydi. Sikl tanasi sekin bo'lsa (masalan, ichida await bilan bazaga yozilsa), keyingi bo'lak so'ralmaydi. Manba to'xtab turadi. Bu backpressure — siz hech narsa qilmasdan.
2.2 break oqimni yopadi
Fayl boshidagi ikki bo'lak kerak, xolos. break qilsak, qolgan fayl nima bo'ladi?
import { createReadStream, writeFileSync } from "node:fs";
writeFileSync("kurslar.csv", "sana,kod,kurs\n2026-10-22,USD,12010\n"
+ "2026-10-22,EUR,13437\n2026-10-23,USD,12030\n");
const fayl = createReadStream("kurslar.csv", { highWaterMark: 16 });
let n = 0;
for await (const bolak of fayl) {
n++;
if (n === 2) break;
}
console.log("bo'laklar:", n, "| yopildimi:", fayl.destroyed);Konsolda:
bo'laklar: 2 | yopildimi: truehighWaterMark: 16 — fayl 16 baytlik bo'laklarda o'qilsin (ko'rish uchun ataylab kichik). break dan keyin oqim yopildi (destroyed: true): fayl deskriptori bo'shadi, qolgan qismi o'qilmadi. for await sikldan chiqishda iteratorning return() metodini chaqiradi. Node oqimi esa bunga javoban o'zini destroy qiladi.
Bu qulay, lekin bitta oqibati bor: break qilingan oqimni qayta o'qib bo'lmaydi. Yana kerak bo'lsa — yangi oqim oching.
Diqqat: Bu qoida faqat siz to'g'ridan-to'g'ri aylangan oqimga tegishli. Oqim boshqa narsaga (masalan
readlinega) o'ralgan bo'lsa,breakfaqat o'ramni yopadi. Biz buni o'lchab topdik — 1-mashqda o'zingiz ko'rasiz.
2.3 Xato try/catch ga tushadi
pipeline darsida .pipe() xatoni yo'qotishini ko'rgan edik. for await esa oqim xatosini sikldan tashlaydi:
import { createReadStream } from "node:fs";
try {
for await (const b of createReadStream("yoq.csv")) {
console.log(b.length);
}
} catch (xato) {
console.log("ushlandi:", xato.code);
}Konsolda:
ushlandi: ENOENTBitta oqim bo'lsa — hammasi joyida. Lekin a.pipe(b) dan keyin b ni for await bilan o'qisangiz, a ning xatosi b ga baribir yetib kelmaydi. Kanondagi faylniOch shuning uchun xatoni qo'lda uzatadi (ochuvchi.destroy(xato)).
Tekshirib ko'ring:
for awaitichidareturn(funksiyadan qaytish) qilsangiz, oqim yopiladimi?
Javob
Ha. break, return va sikl ichidan tashlangan xato — uchalasi ham sikldan erta chiqish. Uchala holatda for await iteratorning return() metodini chaqiradi, Node oqimi esa destroy bo'ladi. Shuning uchun "faylning boshini o'qib, javobni qaytarish" xavfsiz — fayl ochiq qolmaydi.
3. Qatorma-qator: readline va for await
Bo'laklar qator chegarasini hurmat qilmasligini oldingi darsda ko'rdik va flush bilan qatorlarga bo'luvchi yozdik. Real kodda buning tayyori bor — node:readline dagi createInterface. U ham asinxron iterable:
import { createReadStream, writeFileSync } from "node:fs";
import { createInterface } from "node:readline";
writeFileSync("eur.csv", [
"sana,kod,nomi,nominal,kurs,farq",
"2026-10-21,EUR,EVRO,1,13466.99,-58.03",
"2026-10-22,EUR,EVRO,1,13437.27,-29.72",
"2026-10-23,EUR,EVRO,1,13329.15,-108.12",
].join("\r\n") + "\r\n");
const qatorlar = createInterface({
input: createReadStream("eur.csv"),
crlfDelay: Infinity,
});
let min = Infinity;
let raqam = 0;
for await (const qator of qatorlar) {
if (++raqam === 1) continue; // sarlavha
min = Math.min(min, Number(qator.split(",")[4]));
}
console.log("qatorlar:", raqam, "min:", min);Konsolda:
qatorlar: 4 min: 13329.15Kurslar — kanonning haqiqiy CBU javobidan (EUR, 2026-10-21…23). Fayl ataylab Windows uslubidagi qator oxiri bilan yozildi: \r\n (CRLF). crlfDelay: Infinity — \r va \n bitta bo'lakda kelmasa ham, ularni bitta qator oxiri deb hisoblash. Usiz \r bilan \n alohida bo'laklarga tushib qolsa, orada bo'sh qator paydo bo'lishi mumkin. Excel'dan saqlangan CSV deyarli doim CRLF — bu sozlama shuning uchun kerak.
readline qatorlarni chala bo'lak bilan ham to'g'ri yig'adi va oxirgi qatorni \n siz ham beradi. Kanondagi tahlilQil aynan shu sikl ustiga qurilgan.
3.1 Qo'shtirnoqli CSV
qator.split(",") oddiy holatda ishlaydi. Lekin CSV standarti (RFC 4180) maydon ichida vergul bo'lishiga ruxsat beradi — maydon qo'shtirnoqqa olinadi: "AQSH dollari, naqd". Qo'shtirnoqning o'zi esa ikkilanadi: "". Kanondagi csvniAjrat buni belgima-belgi yuradigan kichik holat mashinasi (state machine) bilan hal qiladi. U toza JS — «Ishga tushir» bilan brauzerda sinab ko'ring:
function csvniAjrat(qator) {
const maydonlar = [];
let joriy = "";
let ichida = false; // qo'shtirnoq ichidamizmi
for (let i = 0; i < qator.length; i++) {
const c = qator.charAt(i);
if (ichida) {
if (c === '"' && qator[i + 1] === '"') {
joriy += '"';
i++;
} else if (c === '"') ichida = false;
else joriy += c;
} else if (c === '"') ichida = true;
else if (c === ",") {
maydonlar.push(joriy);
joriy = "";
} else joriy += c;
}
maydonlar.push(joriy);
return maydonlar;
}
console.log(csvniAjrat('a,"b, ""c""",d'));
console.log(csvniAjrat("2026-10-23,USD,12030").length);Konsolda:
[ 'a', 'b, "c"', 'd' ]
3Kanon testi xuddi shu birinchi qatorni tekshiradi. (Kanonda TypeScript; bu yerda tiplari olib tashlangan.) Bitta cheklov: maydon ichida qator oxiri bo'lsa ("birinchi\nikkinchi"), readline uni ikki qatorga bo'lib yuboradi. CBU javobida bunday maydon yo'q — kanon bu holatni ataylab qo'llamadi. Bunday fayllar uchun csv-parse kabi to'liq kutubxona kerak.
Tekshirib ko'ring: Nega
readlinebilan o'qilgan million qatorli fayl xotirani to'ldirmaydi,readFileSync(...).split("\n")esa to'ldiradi?
Javob
readline faylni 64 KB lik bo'laklarda o'qiydi va qatorni bittalab beradi. Xotirada bir vaqtda bitta bo'lak va joriy qator turadi. readFileSync esa butun faylni bitta satrga yuklaydi, split esa undan million qatorli massiv yasaydi — ikkalasi birga xotirada. Aniq raqamlarni katta fayl amaliyoti darsida o'lchaymiz.
4. Web Streams Node'da
4.1 Ikki dunyo
Node'da ikki xil oqim yashaydi:
Node oqimi (node:stream) |
Web Stream (ReadableStream) |
|
|---|---|---|
| Paydo bo'lgan | Node'ning boshidan | brauzer standarti, Node 18 dan global |
| Kim beradi | fs, zlib, net, process.stdin |
fetch javobi, Blob.stream() |
| Ulash | pipeline, .pipe() |
pipeTo, pipeThrough |
| O'zgartiruvchi | Transform |
TransformStream |
Web Stream'lar Node'da global: import siz mavjud. TextDecoderStream, CompressionStream ham shunday. Ulardan yozilgan kod brauzerda ham, Node'da ham, Deno va Bun'da ham ishlaydi.
4.2 toWeb va fromWeb
Node oqimini Web Stream'ga o'girish — Readable.toWeb, teskarisi — Readable.fromWeb:
import { createReadStream, writeFileSync } from "node:fs";
import { Readable } from "node:stream";
writeFileSync("kun.csv", "sana,kod\n2026-10-23,USD\n");
const web = Readable.toWeb(createReadStream("kun.csv"));
console.log("Web Stream'mi:", web instanceof ReadableStream);
let matn = "";
for await (const b of web.pipeThrough(new TextDecoderStream())) {
matn += b;
}
console.log(JSON.stringify(matn));
const javob = new Response("osh\n"); // fetch javobi o'rnida
for await (const b of Readable.fromWeb(javob.body)) {
console.log("Buffer'mi:", Buffer.isBuffer(b), String(b).trim());
}Konsolda:
Web Stream'mi: true
"sana,kod\n2026-10-23,USD\n"
Buffer'mi: true oshnew Response("...") — haqiqiy fetch javobining o'zi: uning body si ReadableStream. Misolda tarmoqqa chiqmaslik uchun javobni qo'lda yasadik. Haqiqiy CBU javobi bilan ham kod aynan shunday bo'ladi.
Amalda eng ko'p kerak bo'ladigan holat: fetch javobini to'g'ridan-to'g'ri faylga yozish. Node 24 dagi pipeline Web Stream'ni ham qabul qiladi — o'girishga ehtiyoj ham yo'q:
import { createWriteStream, readFileSync } from "node:fs";
import { pipeline } from "node:stream/promises";
const javob = new Response("sana,kod\n2026-10-23,USD\n");
await pipeline(javob.body, createWriteStream("javob.csv"));
console.log(readFileSync("javob.csv", "utf8").split("\n").length);Konsolda:
3Uch qator: sarlavha, ma'lumot va oxirgi \n dan keyingi bo'sh satr. Katta faylni yuklab olishda xotirada butun javob emas, faqat joriy bo'lak turadi.
Quyidagi diagrammada CBU javobidan diskkacha bo'lgan yo'lga qarang: chegarada nima o'giriladi.
flowchart LR
A["fetch javobi<br/>ReadableStream"] -->|fromWeb| B["Node oqimi"]
B --> C["createGzip"]
C --> D["createWriteStream"]
E["createReadStream"] -->|toWeb| F["TextDecoderStream"]4.3 TransformStream va CompressionStream
Web Streams'da o'zgartiruvchi — TransformStream. Uni Streams API darsida yozgansiz. Node'dagi Transform bilan solishtiring: transform va flush nomlari bir xil, tayyor callback'i esa yo'q — enqueue bilan beriladi, funksiyadan qaytish — "tayyor". Bu blok brauzerda ham ishlaydi:
const qatorlar = new TransformStream({
start() { this.qoldiq = ""; },
transform(bolak, boshqaruv) {
const qismlar = (this.qoldiq + bolak).split("\n");
this.qoldiq = qismlar.pop();
for (const q of qismlar) boshqaruv.enqueue(q);
},
flush(boshqaruv) {
if (this.qoldiq) boshqaruv.enqueue(this.qoldiq);
},
});
const manba = new ReadableStream({
start(b) {
const bolaklar = ["sana,ko", "d\n2026-10-22,U", "SD"];
for (const s of bolaklar) b.enqueue(s);
b.close();
},
});
const oquvchi = manba.pipeThrough(qatorlar).getReader();
for (;;) {
const { done, value } = await oquvchi.read();
if (done) break;
console.log(value);
}Konsolda:
sana,kod
2026-10-22,USDBrauzerda ishlashi uchun for await o'rniga getReader() ishlatdik — eski Safari'larda ReadableStream ni for await bilan aylanib bo'lmaydi (Streams API darsidagi ogohlantirish). Node 24 da ikkalasi ham ishlaydi.
Siqish uchun ham Web varianti bor — CompressionStream("gzip"). U zlib.createGzip() ning brauzerdagi egizagi. Bu blok ham «Ishga tushir» bilan ishlaydi:
const kun = (i) => String(i + 1).padStart(2, "0");
const csv = "sana,kod,kurs\n" + Array.from({ length: 30 },
(_, i) => `2026-10-${kun(i)},USD,${12000 + i}`).join("\n");
const baytlar = new TextEncoder().encode(csv);
const siqilgan = await new Response(
new Blob([baytlar]).stream()
.pipeThrough(new CompressionStream("gzip")),
).arrayBuffer();
const ochilgan = await new Response(
new Blob([siqilgan]).stream()
.pipeThrough(new DecompressionStream("gzip")),
).text();
console.log("asl:", baytlar.length, "bayt");
const yarmidanKam = siqilgan.byteLength < baytlar.length / 2;
console.log("siqilgan kichikmi:", yarmidanKam);
console.log("qaytdimi:", ochilgan === csv);Konsolda:
asl: 643 bayt
siqilgan kichikmi: true
qaytdimi: trueSiqilgan hajmning aniq raqamini chiqarmadik: brauzer va Node siqish darajasi har xil bo'lishi mumkin. Shu sababli "asl hajmning yarmidan kichikmi?" deb so'radik. Bu brauzer kodi uchun yaxshi odat: natija muhitga bog'liq bo'lsa, aniq qiymatni emas, uning belgisini tekshiring.
Tekshirib ko'ring: Node'da kod yozyapsiz va tanlov bor:
zlib.createGzip()yokiCompressionStream("gzip"). Qaysi birini qachon olasiz?
Javob
Kod faqat Node'da ishlasa va boshqa Node oqimlari (fs, pipeline) bilan ulansa — zlib: u siqish darajasi, zstd, brotli kabi ko'proq sozlamaga ega. Kod brauzerda ham ishlashi kerak bo'lsa (umumiy kutubxona, Next.js'ning ikkala tomoni) — CompressionStream. Kanondagi valyuta — faqat Node CLI, shuning uchun zlib.
5. compose, .map(), .filter()
Node oqimlarida massivga o'xshash yordamchilar bor: .map(), .filter(), .take(), .toArray(). Ular yangi oqim qaytaradi:
import { Readable } from "node:stream";
const juftlar = await Readable.from([1, 2, 3, 4])
.filter((x) => x % 2 === 0)
.map((x) => x * 10)
.toArray();
console.log(juftlar);Konsolda:
[ 20, 40 ]stream.compose(...) esa bir nechta bosqichni (oqim yoki async generator) bitta oqimga yig'adi. pipeline dan farqi: u zanjirni yurgizmaydi, balki keyin ulash mumkin bo'lgan Duplex oqim qaytaradi:
import { Readable, compose } from "node:stream";
const kattaHarf = compose(async function* (manba) {
for await (const b of manba) yield String(b).toUpperCase();
});
for await (const b of Readable.from(["usd"]).pipe(kattaHarf)) {
console.log(String(b));
}Konsolda:
USDDiqqat: Node 24 hujjatiga ko'ra (2026-10 holatiga)
.map()/.filter()/.toArray()— hamon Stability: 1 — Experimental (tajribaviy). Bu API kelajakdagi versiyada o'zgarishi mumkin.stream.composeesa faqat Node 24.19.0 da barqaror deb belgilandi — undan eski Node 24 da u ham tajribaviy. Biz ishga tushirganda Node 24.21.0 ogohlantirish chiqarmadi. Kanon ularni ishlatmadi: o'rnigafor awaitvapipeline+ async generator — har qanday Node 24 da barqaror API.
Bir narsani adashtirmang: Iterator helpers darsidagi Iterator.prototype.map — boshqa narsa, u sinxron iteratorlar uchun va standart. Oqimdagi .map() — Node'ga xos.
6. Qaysi o'qish usuli qachon
| Usul | Qachon |
|---|---|
for await (const b of oqim) |
natijani yig'asiz (statistika, qidiruv), oxirida bitta javob |
pipeline(a, b, c) |
oqimdan oqimga (faylga, siqishga, tarmoqqa) |
on("data") |
eski kod; yangi kodda deyarli kerak emas |
getReader() |
brauzer kodi, eski Safari'ni ham qo'llash kerak |
Kanondagi valyuta ikkalasini ishlatadi: tahlil — for await (natija — jadval), tarix — pipeline (natija — fayl yoki ekran).
7. Ko'p uchraydigan xatolar
7.1 Bitta oqimni ikki marta o'qish
// ❌ ikkinchi sikl hech narsa olmaydi
for await (const q of qatorlar) sanoq++;
for await (const q of qatorlar) yigindi += Number(q);Birinchi sikl oqimni oxirigacha o'qidi (yoki break bilan yopdi). Oqim — bir martalik, xuddi musluk suvi kabi. Ikkala hisobni bitta siklda qiling yoki faylni qayta oching.
7.2 crlfDelay ni unutish
Windows'da saqlangan CSV'da \r qolib ketadi: oxirgi maydon "-108.12\r" bo'ladi. Number("12030\r") — bu holatda ishlaydi (bo'shliq kesiladi), lekin kod === "USD" kabi satr taqqoslash jim false beradi. crlfDelay: Infinity, oxirgi maydonni .trim().
7.3 Web Stream'ni .pipe() ga berish
javob.body.pipe(...) — TypeError: javob.body.pipe is not a function. Web Stream'da .pipe yo'q, faqat pipeTo/pipeThrough. pipeline(javob.body, ...) yoki Readable.fromWeb(javob.body).
7.4 Experimental API'ga tayanish
.map() zanjiri chiroyli ko'rinadi, lekin Stability 1. Kutubxona yoki uzoq yashaydigan loyihada — for await yoki generator. Kichik skriptda ishlatsangiz, Node versiyasini engines da qat'iy yozing.
8. Mashqlar
1-mashq (oson): Birinchi USD
Fayldan birinchi USD qatorini readline bilan toping va siklni break bilan to'xtating. Keyin fayl oqimi yopilganini tekshiring (fayl.destroyed). Kurs faylini «break oqimni yopadi» bo'limidagidek writeFileSync bilan o'zingiz yarating. Natija sizni ajablantirishi mumkin — keyin uni tuzating.
Yechim
Avval to'g'ridan-to'g'ri urinish. Biz ishga tushirdik (Node 24.21.0, 200 000 qatorli fayl):
const fayl = createReadStream("big.csv");
const rl = createInterface({ input: fayl, crlfDelay: Infinity });
let n = 0;
for await (const q of rl) { if (++n === 3) break; }
console.log("break, destroyed:", fayl.destroyed);break, destroyed: falseBu — haqiqiy tuzoq. break readline interfeysini yopadi, lekin uning kirish oqimini (fayl) yopmaydi. 100 ms dan keyin ham false edi. «break oqimni yopadi» bo'limidagi qoida faqat to'g'ridan-to'g'ri aylanilgan oqimga tegishli. Bu yerda biz rl ni aylandik, fayl ni emas. Fayl ochiq qoladi — uzoq ishlaydigan server'da bunday ochiq fayllar to'planadi. Tuzatish — finally ichida o'zingiz yopish:
import { createReadStream, writeFileSync } from "node:fs";
import { createInterface } from "node:readline";
writeFileSync("k.csv", "sana,kod,kurs\n2026-10-22,EUR,13437\n"
+ "2026-10-22,USD,12010\n2026-10-23,USD,12030\n");
const fayl = createReadStream("k.csv");
let topildi = null;
try {
const rl = createInterface({ input: fayl, crlfDelay: Infinity });
for await (const q of rl) {
if (q.split(",")[1] === "USD") {
topildi = q;
break;
}
}
} finally {
fayl.destroy();
}
console.log(topildi);
console.log("fayl yopildimi:", fayl.destroyed);Konsolda:
2026-10-22,USD,12010
fayl yopildimi: truefinally sikl qanday tugashidan qat'i nazar (oxirigacha, break yoki xato bilan) bajariladi. Kanondagi tahlilQil faylni doim oxirigacha o'qiydi, shuning uchun unga bu tuzoq tegmaydi.
2-mashq (o'rta): Har valyuta bo'yicha o'rtacha
ortacha(oqim) async funksiyasini yozing: CSV qatorlar oqimini (sana,kod,kurs) for await bilan o'qib, { USD: 12020, EUR: 13437 } ko'rinishidagi obyekt qaytarsin. Sarlavha tashlansin. Sinov: Readable.from(["sana,kod,kurs", "2026-10-22,USD,12010", "2026-10-22,EUR,13437", "2026-10-23,USD,12030"]). Eslatma: Map yoki oddiy obyekt bilan yig'indi va son.
Yechim
import { Readable } from "node:stream";
async function ortacha(oqim) {
const jami = new Map(); // kod -> { yigindi, soni }
let birinchi = true;
for await (const qator of oqim) {
if (birinchi) { birinchi = false; continue; }
const [, kod, kurs] = qator.split(",");
const s = jami.get(kod) ?? { yigindi: 0, soni: 0 };
s.yigindi += Number(kurs);
s.soni++;
jami.set(kod, s);
}
const natija = {};
for (const [kod, s] of jami) natija[kod] = s.yigindi / s.soni;
return natija;
}
console.log(await ortacha(Readable.from(["sana,kod,kurs",
"2026-10-22,USD,12010", "2026-10-22,EUR,13437",
"2026-10-23,USD,12030"])));Konsolda:
{ USD: 12020, EUR: 13437 }Bu kanondagi tahlilQil ning soddalashtirilgan ko'rinishi. Kanon qo'shimcha ravishda nominalni hisobga oladi (IDR uchun Nominal — 10), min/max va sana oralig'ini saqlaydi.
3-mashq (qiyin): Javobni siqib saqlash
saqlaGzip(javob, fayl) funksiyasini yozing: fetch javobining tanasini (javob.body) gzip bilan siqib faylga yozsin, faqat javob.ok bo'lsa. Aks holda HTTP <status> xabarli xato tashlasin. Sinov uchun ikki soxta javob: new Response("sana,kod\n2026-10-23,USD\n") va new Response("yo'q", { status: 404 }). Birinchisidan keyin faylni gunzipSync bilan ochib, qatorlar sonini chiqaring. Eslatma: createGzip, gunzipSync — zlib; pipeline Web Stream'ni ham qabul qiladi («toWeb va fromWeb» bo'limi).
Yechim
import { createWriteStream, readFileSync } from "node:fs";
import { pipeline } from "node:stream/promises";
import { createGzip, gunzipSync } from "node:zlib";
async function saqlaGzip(javob, fayl) {
if (!javob.ok) throw new Error(`HTTP ${javob.status}`);
await pipeline(javob.body, createGzip(), createWriteStream(fayl));
}
const javob = new Response("sana,kod\n2026-10-23,USD\n");
await saqlaGzip(javob, "k.csv.gz");
const matn = gunzipSync(readFileSync("k.csv.gz")).toString();
console.log("qatorlar:", matn.trimEnd().split("\n").length);
try {
await saqlaGzip(new Response("yo'q", { status: 404 }), "x.csv.gz");
} catch (xato) {
console.log(xato.message);
}Konsolda:
qatorlar: 2
HTTP 404javob.ok tekshiruvi oqimdan oldin: aks holda 404 sahifaning HTML'i siqilib, "kurslar" fayli deb saqlanardi. Haqiqiy loyihada yarim fayl qolmasligi uchun atomik yozish ham qo'shiladi.
4-mashq: CLI qadami — tahlilQil
Bu dars holat 35 ning bir qismi; hali commit qilmang — commit katta fayl amaliyoti darsida. src/tahlil.ts ga ikki funksiya qo'shing:
csvniAjrat(qator)— «Qo'shtirnoqli CSV» bo'limidagi funksiya (TypeScript tiplari bilan).tahlilQil(kirish: Readable)—createInterface({ input: kirish, crlfDelay: Infinity })+for await. Birinchi qatorCSV_SARLAVHAga teng bo'lmasa — xato. Har qatordan birlik kursi:Number(kurs) / Number(nominal); son bo'lmasa — qator raqami bilan xato. Har valyuta uchun soni, min, max, o'rtacha, birinchi va oxirgi sana.
Yechim
Kanondagi asosiy sikl (src/tahlil.ts, qisqartirilgan — Map ga yozish qismi tushirib qoldirildi):
let raqam = 0;
const qatorlar = createInterface({
input: kirish,
crlfDelay: Infinity,
});
for await (const qator of qatorlar) {
raqam++;
if (raqam === 1) {
if (qator !== CSV_SARLAVHA) throw new Error(
`1-qator "${CSV_SARLAVHA}" bo'lishi kerak`);
continue;
}
if (qator === "") continue;
const [sana = "", kod = "", , nominalM = "", kursM = ""] =
csvniAjrat(qator);
const birlik = Number(kursM) / Number(nominalM);
if (!Number.isFinite(birlik))
throw new Error(`${String(raqam)}-qator: kurs son emas`);
// ... jami.get(kod) — soni, min, max, yigindi, birinchi, oxirgi
}, , — destrukturlashda uchinchi maydonni (nomi) o'tkazib yuborish. Number.isFinite NaN ni ham, nolga bo'lishdan chiqqan Infinity ni ham ushlaydi. Kanon testlari: noto'g'ri sarlavha → /1-qator/ bilan rad etiladi; csvniAjrat('a,"b, ""c""",d') → ["a", 'b, "c"', "d"].
9. Real ishda
- Log tahlili: gigabaytli log faylda xatolarni sanash, eng sekin so'rovlarni topish —
readline+for awaitning klassik ishi. Skriptlar va CLI vositalar shunday yoziladi. - Fayl yuklash:
fetchjavobini diskka yozish (yangilanishlar, zaxira, rasmlar) —pipeline(javob.body, createWriteStream(...)). - Universal kod: Next.js, Cloudflare Workers, Deno, Bun — hammasi Web Streams'ni biladi. Ikkala muhitda ishlaydigan kutubxona yozsangiz, Web Streams asosiy tanlov bo'ladi.
- Intervyu savollari: "Oqimni
for awaitbilan o'qisa, backpressure bormi?", "breakdan keyin oqim nima bo'ladi?", "Node stream va Web Stream farqi?".
Xulosa
- Har Node Readable — asinxron iterable:
for awaitbo'lakni bittalab tortadi, backpressure o'z-o'zidan. break/returnaylanilgan oqimni yopadi (readlineichidagi faylni emas —finallydadestroy); oqim xatositry/catchga tushadi.- Qatorma-qator o'qish —
readline.createInterface({ input, crlfDelay: Infinity }). Readable.toWeb/fromWeb— Node va Web oqimlari orasida ko'prik;pipelineWeb Stream'ni ham qabul qiladi..map(),.filter()— Node 24 da experimental,compose— 24.19.0 dan barqaror; kanon kodidafor awaitva generatorlar.
Keyingi dars: Amaliyot: katta faylni stream bilan qayta ishlash — 1 million qatorli CSV'ni stream bilan va butun holda o'qib, xotira va vaqtni o'lchaymiz, holat 35 ni commit qilamiz.
Manbalar
- Node.js hujjati: Readable streams — async iterator (v24).
- Node.js hujjati: Web Streams API.
- Node.js hujjati: readline — Example: Read file stream line-by-Line.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!