Mundarija (27)
- Bu darsda
- 1. Nega bu kerak?
- 2. O'z Readable oqimingiz
- 2.1 read() va push()
- 2.2 Faqat satr yoki bayt
- 2.3 objectMode: baytlar o'rniga obyektlar
- 3. Readable.from: tayyor manbadan oqim
- 4. Transform: yo'lda o'zgartirish
- 4.1 transform funksiyasi
- 4.2 Qator o'rtasida uzilgan bo'lak va flush
- 4.3 Klass bilan yozish
- 5. Butun konveyer
- 6. Klass, funksiya yoki generator?
- 7. Ko'p uchraydigan xatolar
- 7.1 tayyor() ni chaqirmaslik
- 7.2 tayyor ni ikki marta chaqirish
- 7.3 objectMode siz obyekt
- 7.4 flush ni unutish
- 7.5 Strelka funksiyada this.push
- 8. Mashqlar
- 1-mashq (oson): Sanovchi Readable
- 2-mashq (o'rta): Valyuta filtri
- 3-mashq (qiyin): Qatorlar sanog'i bilan CSV
- 4-mashq: CLI qadami — matnga generatori
- 9. Real ishda
- Xulosa
- Manbalar
Node.js Transform stream yozish: o'z Readable va Transform oqimingiz
Qisqacha: O'z oqimingizni yasashning uch yo'li bor. Readable —
read()ichidathis.push(bo'lak)qilasiz, oxiridapush(null). Transform —transform(bo'lak, kodlash, tayyor)har bo'lakni o'zgartiradi,flush(tayyor)esa oxirida qolganini chiqaradi. Eng qisqa yo'l —Readable.from(generator)vapipelineichidagi async generator. Bayt o'rniga obyekt uzatish uchunobjectMode: truekerak.
Bu darsda
new Readable({ read })bilan ma'lumot manbai yasay olasiz.new Transform({ transform, flush })bilan bo'laklarni yo'lda o'zgartira olasiz.- Qator o'rtasida uzilgan bo'laklarni
flushbilan to'g'ri yig'asiz. objectModeqachon kerakligini va uninghighWaterMarkfarqini tushuntira olasiz.- Transform klass va async generator orasidan asos bilan tanlaysiz.
Oldin bilishingiz kerak: Stream nima: to'rt turi va rejimlari, pipeline, finished va stream xatolari, Asinxron iteratsiya, Streams API.
1. Nega bu kerak?
Oldingi darsda tayyor oqimlarni pipeline bilan uladik: createReadStream, createGunzip, createWriteStream. Ular Node'ning o'zida bor. Lekin ishning eng qiziq qismi ko'pincha o'rtada bo'ladi: CSV qatorini obyektga aylantirish, keraksiz valyutalarni tashlash, natijani JSON'ga o'girish. Buni hech bir tayyor modul siz uchun qilmaydi.
valyuta CLI'da aynan shunday zanjir bor. valyuta tarix EUR --dan 2026-09-20 -f json kunma-kun kurslarni oladi va ularni matnga aylantirib, faylga yozadi. Kurslar — obyektlar, fayl esa baytlar kutadi. Orada kimdir obyektni matnga o'girishi kerak.
Hayotiy o'xshatish: non zavodi konveyeri. Bir tomonda un qopi (manba), oxirida qadoqlangan non (yozish). O'rtada stanoklar turadi: xamir qorish, kesish, pishirish. Har stanok bitta ishni qiladi va tayyorini keyingisiga uzatadi. Bugun siz o'z stanogingizni yasaysiz va uni konveyerga qo'yasiz.
Streams API darsida brauzerda o'z TransformStream ingizni yozgan edingiz — qatorlarga bo'luvchi. G'oya o'sha. Faqat Node'ning o'z oqimlari (node:stream) biroz boshqacha yoziladi va ular Node ekotizimida hamma joyda ishlatiladi.
2. O'z Readable oqimingiz
2.1 read() va push()
Readable — ma'lumot manbai. O'z manbangizni yasash uchun read() funksiyasini berasiz. Node uni "menga yana ma'lumot kerak" degan paytda chaqiradi. Siz this.push(bo'lak) bilan bo'lak berasiz. Ma'lumot tugaganda — this.push(null):
import { Readable } from "node:stream";
let kun = 20;
const kunlar = new Readable({
read() {
if (kun > 23) {
this.push(null); // oqim tugadi
return;
}
this.push(`2026-10-${kun}\n`);
kun++;
},
});
kunlar.on("data", (b) => {
console.log("bo'lak:", JSON.stringify(String(b)));
});
kunlar.on("end", () => console.log("tugadi"));Konsolda:
bo'lak: "2026-10-20\n"
bo'lak: "2026-10-21\n"
bo'lak: "2026-10-22\n"
bo'lak: "2026-10-23\n"
tugadiIkki narsaga e'tibor bering. Birinchi: biz read() ni o'zimiz chaqirmadik — uni Node chaqirdi, chunki data tinglovchisi ma'lumot so'radi. Ikkinchi: String(b) kerak bo'ldi. Oddiy oqim satrni Buffer ga aylantirib uzatadi (Buffer darsini eslang).
push() ning qaytargan qiymati ham bor. U false qaytarsa — ichki bufer to'ldi, ko'proq bermang. Bu backpressure darsidagi write() ning teskari tomoni. read() ichida bitta bo'lak berib qaytsangiz, bu qoida o'z-o'zidan bajariladi: Node keyingi bo'lakni o'zi so'raydi.
2.2 Faqat satr yoki bayt
Oddiy oqimga son bersangiz nima bo'ladi?
import { Readable } from "node:stream";
const r = new Readable({ read() {} });
r.on("error", (x) => console.log(x.code));
r.push(12010);Konsolda:
ERR_INVALID_ARG_TYPETo'liq xabar shunday:
The "chunk" argument must be of type string or an instance of Buffer, TypedArray, or DataView. Received type number (12010)Tarjimasi: "chunk (bo'lak) argumenti satr yoki Buffer, TypedArray, DataView nusxasi bo'lishi kerak. Son (12010) berildi". Oqim sukut bo'yicha baytlar uchun mo'ljallangan — u fayl, tarmoq va siqish bilan ishlaydi. Son, obyekt, massiv uzatish uchun maxsus rejim kerak.
2.3 objectMode: baytlar o'rniga obyektlar
objectMode: true — oqimga "har bo'lak — bitta JS qiymati" deb aytish. Endi push({ kod: "USD" }) ham, push(12010) ham mumkin. Faqat null mumkin emas — u "oqim tugadi" degan belgi.
Bu rejimda highWaterMark (bufer chegarasi) ma'nosi ham o'zgaradi:
import { Readable } from "node:stream";
const baytli = new Readable({ read() {} });
const obyektli = new Readable({ objectMode: true, read() {} });
console.log("bayt oqimi:", baytli.readableHighWaterMark);
console.log("obyekt oqimi:", obyektli.readableHighWaterMark);Konsolda:
bayt oqimi: 16384
obyekt oqimi: 16Bayt oqimida bufer 16 384 bayt (16 KB) — bu Windows'dagi sukut; Linux/macOS'da 65 536 (Stream nima darsidagi jadval). Obyekt oqimida — 16 ta obyekt, ularning hajmidan qat'i nazar. Har obyekt 1 MB bo'lsa, buferda 16 MB yotadi. Katta obyektlar bilan ishlasangiz, highWaterMark ni kichraytiring.
Tekshirib ko'ring:
read()ichidathis.push(null)ni unutsangiz nima bo'ladi?
Javob
Oqim hech qachon end hodisasini chiqarmaydi. for await yoki pipeline uni o'qiyotgan bo'lsa, ular "yana ma'lumot keladi" deb abadiy kutadi — xuddi oldingi darsdagi .pipe() tuzog'i kabi. push(null) — "tugadi" degan yagona signal.
3. Readable.from: tayyor manbadan oqim
new Readable({ read }) ko'p hollarda ortiqcha. Ma'lumot massivda yoki generatorda bo'lsa, Readable.from uni bitta qatorda oqimga aylantiradi:
import { Readable } from "node:stream";
for await (const b of Readable.from(["osh", "manti"])) {
console.log("massiv:", b);
}
for await (const b of Readable.from("salom")) console.log("satr:", b);
const r = Readable.from([1, 2]);
console.log(r.readableObjectMode, r.readableHighWaterMark);Konsolda:
massiv: osh
massiv: manti
satr: salom
true 1Uchta tuzoqqa qarang:
Readable.fromsukut bo'yicha objectMode da ishlaydi (true). Massivdagi har element — alohida bo'lak,Bufferemas.- Satr bersangiz, u harflarga bo'linmaydi:
"salom"— bitta bo'lak. (Satr ham iterable, lekinReadable.fromuni ataylab butun uzatadi.) - Bu yerda
highWaterMark— 1. Generator bitta qiymatni oldindan tayyorlaydi, keyingisini so'ralganda.
Eng kuchli varianti — async generator dan oqim. Asinxron iteratsiya darsida yozgan async function* bevosita oqimga aylanadi:
import { Readable } from "node:stream";
import { setTimeout as kut } from "node:timers/promises";
async function* kurslar() {
for (const [kod, kurs] of [["USD", 12010], ["EUR", 13437]]) {
await kut(10); // tarmoq so'rovi o'rnida
yield { kod, kurs };
}
}
for await (const k of Readable.from(kurslar())) console.log(k);Konsolda:
{ kod: 'USD', kurs: 12010 }
{ kod: 'EUR', kurs: 13437 }Generator qachon chaqirilishini oqim boshqaradi. Qabul qiluvchi sekin bo'lsa, generator yield da to'xtab turadi — backpressure o'z-o'zidan ishlaydi. Kanonda valyuta tarix aynan shunday quriladi: tarixOqimi — kunma-kun CBU'dan so'raydigan async generator.
4. Transform: yo'lda o'zgartirish
4.1 transform funksiyasi
Transform — ham yoziladigan, ham o'qiladigan oqim: bir tomondan kiradi, o'zgarib, ikkinchi tomondan chiqadi. Yasash uchun transform(bolak, kodlash, tayyor) funksiyasini berasiz:
bolak— kirgan bo'lak;kodlash— matn kodlashi (masalan"utf8"); ko'pincha kerak emas, shuning uchun_koddeb nomlaymiz;tayyor(xato, natija)— "bu bo'lak bilan ishim tugadi" degan callback.
tayyor ni chaqirishning uch shakli bor:
| Chaqiruv | Ma'nosi |
|---|---|
tayyor(null, natija) |
bitta natija chiqar va keyingisini ber |
tayyor() |
bu bo'lakdan hech narsa chiqmaydi (filtr) |
tayyor(new Error(...)) |
xato — pipeline hammasini yopadi |
Bitta bo'lakdan bir nechta natija chiqarish kerak bo'lsa, this.push(...) ni xohlagancha chaqirasiz, oxirida tayyor(). Eng oddiy misol — sonni ikkiga ko'paytirish:
import { Transform } from "node:stream";
const ikkilantir = new Transform({
objectMode: true,
transform(son, _kod, tayyor) { tayyor(null, son * 2); },
});
ikkilantir.on("data", (x) => console.log("chiqdi:", x));
ikkilantir.write(5);
ikkilantir.write(7);
ikkilantir.end();Konsolda:
chiqdi: 10
chiqdi: 144.2 Qator o'rtasida uzilgan bo'lak va flush
Diskdan yoki tarmoqdan kelgan bo'laklar qator chegarasini hurmat qilmaydi. 64 KB bo'lak qatorning o'rtasida uzilishi mumkin. Qatorlarga bo'luvchi Transform chala qismni qoldiq sifatida saqlab, keyingi bo'lakka qo'shishi kerak. Avval algoritmning o'zini oddiy JS'da qadamma-qadam ko'ramiz:
Oxirgi qadamga qarang. Fayl \n bilan tugamasa, oxirgi qator qoldiqda qolib ketadi. Transform'da buning uchun alohida joy bor — flush(tayyor). U kirish tugagandan keyin, oqim yopilishidan oldin bir marta chaqiriladi. Ikkala variantni solishtiramiz (fayl \n siz tugaydi):
import { Readable, Transform } from "node:stream";
// bo'laklar qator o'rtasida uziladi — xuddi diskdan o'qilgandek
const bolaklar = ["sana,ko", "d,kurs\n2026-10-22,U",
"SD,12010\n2026-10-23,USD,12030"];
function qatorAjratuvchi(flushBilan) {
let qoldiq = "";
return new Transform({
readableObjectMode: true,
transform(bolak, _kod, tayyor) {
const qismlar = (qoldiq + bolak).split("\n");
qoldiq = qismlar.pop(); // oxirgisi chala bo'lishi mumkin
for (const q of qismlar) this.push(q);
tayyor();
},
flush: flushBilan
? function (tayyor) { if (qoldiq) this.push(qoldiq); tayyor(); }
: undefined,
});
}
for (const f of [false, true]) {
const qatorlar = [];
const oqim = Readable.from(bolaklar).pipe(qatorAjratuvchi(f));
for await (const q of oqim) qatorlar.push(q);
console.log(f ? "flush bilan:" : "flush'siz:", qatorlar.length);
}Konsolda:
flush'siz: 2
flush bilan: 3flush siz oxirgi kurs (2026-10-23,USD,12030) jim yo'qoldi — xato ham, ogohlantirish ham yo'q. Bu eng xavfli xato turi: natija to'g'riga o'xshaydi, lekin bitta qator kam.
Yana ikki yangi narsa bor. readableObjectMode: true — faqat chiqish tomoni obyekt rejimida: kirishga baytlar keladi, chiqishga satrlar (qatorlar) ketadi. Uning jufti — writableObjectMode. flush esa function bilan yozildi, strelka funksiya bilan emas: ichida this.push kerak, strelka funksiyada esa this oqimga bog'lanmaydi.
Maslahat: Node'da tayyor qatorlarga bo'luvchi bor —
node:readlinedagicreateInterface(readline va terminal). Kanondagivalyuta tahlilaynan shuni ishlatadi. O'zingiz yozish —flushni tushunish uchun mashq; real kodda tayyor, sinalgan vositani oling.
Tekshirib ko'ring:
flushqachon chaqiriladi: har bo'lakdan keyinmi yoki bir martami?
Javob
Bir marta — kirish tomoni tugaganda (end() chaqirilganda yoki manba tugaganda), chiqish tomoni yopilishidan oldin. Shuning uchun u "oxirida qolgan narsani chiqarish" uchun ishlatiladi: chala qator, JSON massivning yopuvchi ], siqish algoritmining oxirgi baytlari. createGzip ham ichkarida xuddi shunday qiladi.
4.3 Klass bilan yozish
Murakkabroq Transform'ni klass sifatida yozish qulay: holat (state) xususiyatlarda turadi, metodlar nomlangan. Bunda funksiya nomlari pastki chiziq bilan boshlanadi — _transform va _flush. Ularni Node chaqiradi, siz emas:
import { Readable, Transform } from "node:stream";
import { pipeline } from "node:stream/promises";
class CsvObyekt extends Transform {
#sarlavha = null;
constructor() {
super({ objectMode: true });
}
_transform(qator, _kod, tayyor) {
const maydonlar = qator.split(",");
if (this.#sarlavha === null) {
this.#sarlavha = maydonlar;
return tayyor();
}
const yozuv = {};
this.#sarlavha.forEach((nom, i) => {
yozuv[nom] = maydonlar[i];
});
tayyor(null, yozuv);
}
}
const qatorlar = ["sana,kod,kurs", "2026-10-22,USD,12010",
"2026-10-22,EUR,13437"];
await pipeline(Readable.from(qatorlar), new CsvObyekt(),
async function* (oqim) {
for await (const y of oqim) console.log(y);
});Konsolda:
{ sana: '2026-10-22', kod: 'USD', kurs: '12010' }
{ sana: '2026-10-22', kod: 'EUR', kurs: '13437' }Birinchi qator — sarlavha: u saqlanadi va chiqarilmaydi (tayyor() natijasiz). Qolganlari sarlavha nomlari bilan obyektga aylanadi. #sarlavha — klassning yopiq maydoni, uni tashqaridan hech kim o'zgartira olmaydi.
Bu split(",") qo'shtirnoq ichidagi vergulni bilmaydi ("AQSH dollari, naqd"). Kanon buning uchun RFC 4180 bo'yicha csvniAjrat funksiyasini yozgan — uni keyingi darsda ko'ramiz.
5. Butun konveyer
Endi hammasini bitta zanjirga yig'amiz: obyektlar → JSON matn. Bu yerda flush boshqa ish qiladi — massivni yopadi. Diagrammada har bosqichda nima oqayotganiga qarang:
flowchart LR
A["Readable.from<br/>obyektlar"] -->|"obyekt"| B["jsonMassiv"]
B -->|"matn bo'laklari"| C["yozish / ekran"]
B -. "flush: ]" .-> Cimport { Readable, Transform } from "node:stream";
import { pipeline } from "node:stream/promises";
function jsonMassiv() {
let birinchi = true;
return new Transform({
writableObjectMode: true,
transform(yozuv, _kod, tayyor) {
const boshi = birinchi ? "[\n " : ",\n ";
birinchi = false;
tayyor(null, boshi + JSON.stringify(yozuv));
},
flush(tayyor) {
tayyor(null, birinchi ? "[]\n" : "\n]\n");
},
});
}
const yozuvlar = [
{ kod: "USD", kurs: 12010 },
{ kod: "EUR", kurs: 13437 },
];
let matn = "";
const manba = Readable.from(yozuvlar);
await pipeline(manba, jsonMassiv(), async (oqim) => {
for await (const b of oqim) matn += b;
});
console.log(matn.trimEnd());
console.log(JSON.parse(matn).length);Konsolda:
[
{"kod":"USD","kurs":12010},
{"kod":"EUR","kurs":13437}
]
2Nega JSON.stringify(yozuvlar) qilib qo'ya qolmadik? Chunki u butun massiv xotirada bo'lishini talab qiladi. Bu Transform esa million yozuvni ham bittalab o'tkazadi: xotirada faqat joriy yozuv turadi. flush dagi birinchi ? "[]\n" — oqim bo'sh bo'lsa ham yaroqli JSON chiqishi uchun. Bo'sh kunlar oralig'i so'ralsa, foydalanuvchi buzuq [ emas, [] oladi.
pipeline ning oxirgi bo'g'ini — oddiy async funksiya (generator emas). Bunday funksiya oqimni o'qib tugatadi va "qabul qiluvchi" o'rnida turadi.
6. Klass, funksiya yoki generator?
Bir xil ishni uch xil yozish mumkin. Yuqoridagi CsvObyekt ni async generator bilan yozsak:
async function* csvObyekt(qatorlar) {
let sarlavha = null;
for await (const qator of qatorlar) {
const maydonlar = qator.split(",");
if (sarlavha === null) { sarlavha = maydonlar; continue; }
const juftlar = sarlavha.map((n, i) => [n, maydonlar[i]]);
yield Object.fromEntries(juftlar);
}
}flush kerak emas: sikldan keyingi kod — o'sha flush. tayyor callback'i ham yo'q: yield — natija, throw — xato, continue — filtr.
| Usul | Qachon tanlanadi |
|---|---|
| async generator | ko'p holat; qisqa, o'qish oson, test qilish oson |
new Transform({...}) |
.pipe() kerak yoki boshqa API aynan oqim kutadi |
class extends Transform |
kutubxona yozasiz, qayta ishlatiladigan sozlamali oqim |
Generatorning bitta cheklovi bor: u pipeline ichida yoki Readable.from orqali ishlaydi. Funksiyadan oqim qaytarish kerak bo'lsa (masalan, faylniOch kabi), Transform kerak bo'ladi — chunki generator .pipe() ga ulanmaydi.
Kanon qaysi yo'lni tanladi? valyuta da bitta ham class extends Transform yo'q. Hamma oraliq ish — async generatorlar: tarixOqimi (kurslar), matnga (matn bo'laklari). Ular Readable.from(...) bilan oqimga aylanib, pipeline ga kiradi. Sabab — TypeScript tiplari va testlar: generatorni oqimsiz ham, oddiy massiv bilan sinash mumkin.
7. Ko'p uchraydigan xatolar
7.1 tayyor() ni chaqirmaslik
// ❌ birinchi bo'lakdan keyin oqim to'xtab qoladi
transform(bolak, _kod, tayyor) {
this.push(bolak);
}Node keyingi bo'lakni bermaydi — tayyor signalini kutyapti. Xato yo'q, natija yo'q. Har yo'lda aynan bir marta tayyor() yoki tayyor(null, natija).
7.2 tayyor ni ikki marta chaqirish
// ❌ filtr shartida return unutilgan
if (yozuv.kod !== "USD") tayyor();
tayyor(null, yozuv);Natija: Error [ERR_MULTIPLE_CALLBACK]: Callback called multiple times. return tayyor(); — qaytish bilan.
7.3 objectMode siz obyekt
tayyor(null, { kurs: 1 }) oddiy Transform'da ERR_INVALID_ARG_TYPE beradi — «Faqat satr yoki bayt» bo'limidagi xato. objectMode: true yoki chiqish tomonigagina — readableObjectMode: true.
7.4 flush ni unutish
Oxirgi chala qator, JSON'ning yopuvchi ] — jim yo'qoladi. "Transform'da qoldiq bormi?" degan savol — har code review'da.
7.5 Strelka funksiyada this.push
// ❌ this — Transform emas
flush: (tayyor) => { this.push(qoldiq); tayyor(); },Strelka funksiyaning this i tashqi kontekstdan olinadi. ESM modulda u undefined — TypeError: Cannot read properties of undefined (reading 'push'). flush(tayyor) { ... } yoki function (tayyor) { ... }. Yoki natijani tayyor(null, qoldiq) bilan bering — this umuman kerak bo'lmaydi.
8. Mashqlar
1-mashq (oson): Sanovchi Readable
Quyidagi oqim nima chiqaradi? Avval o'ylang, keyin o'z kompyuteringizda node bilan tekshiring.
import { Readable } from "node:stream";
let qolgan = 3;
const sanoq = new Readable({
objectMode: true,
read() {
this.push(qolgan > 0 ? qolgan-- : null);
},
});
for await (const son of sanoq) console.log(son);
console.log("start!");Konsolda:
3
2
1
start!Yechim
qolgan-- avval qiymatni beradi, keyin kamaytiradi: 3, 2, 1. Qiymat 0 bo'lganda push(null) — oqim tugaydi va for await dan chiqiladi. objectMode bo'lmasa, push(3) son bo'lgani uchun ERR_INVALID_ARG_TYPE berardi.
2-mashq (o'rta): Valyuta filtri
faqat(kod) funksiyasini yozing: u objectMode Transform qaytarsin va faqat yozuv.kod === kod bo'lgan yozuvlarni o'tkazsin. Kod katta-kichik harfga sezgir bo'lmasin ("usd" ham ishlasin). Sinov: Readable.from([{kod:"USD"},{kod:"EUR"},{kod:"USD"}]) → faqat("usd") → nechta yozuv o'tdi? Eslatma: filtrda tayyor() natijasiz chaqiriladi («transform funksiyasi» bo'limidagi jadval).
Yechim
import { Readable, Transform } from "node:stream";
import { pipeline } from "node:stream/promises";
function faqat(kod) {
const kerak = kod.toUpperCase();
return new Transform({
objectMode: true,
transform(yozuv, _kod, tayyor) {
if (yozuv.kod !== kerak) return tayyor();
tayyor(null, yozuv);
},
});
}
let soni = 0;
await pipeline(
Readable.from([{ kod: "USD" }, { kod: "EUR" }, { kod: "USD" }]),
faqat("usd"),
async function (oqim) { for await (const _ of oqim) soni++; },
);
console.log("o'tdi:", soni);Konsolda:
o'tdi: 2return tayyor() — qaytish bilan: aks holda keyingi qatordagi tayyor(null, yozuv) ham chaqirilib, ERR_MULTIPLE_CALLBACK bo'lardi.
3-mashq (qiyin): Qatorlar sanog'i bilan CSV
qatorRaqami() Transform'ini yozing. U matn bo'laklarini olsin (qator o'rtasida uzilgan bo'lishi mumkin) va har qatorni "1: sana,kod" ko'rinishida chiqarsin. Oxirgi qator \n siz tugasa ham yo'qolmasin, bo'sh qatorlar o'tkazib yuborilsin. Sinov bo'laklari: ["sana,k", "od\n\n2026-10-23,U", "SD"]. Eslatma: flush va qoldiq — «Qator o'rtasida uzilgan bo'lak va flush» bo'limida.
Yechim
import { Readable, Transform } from "node:stream";
import { pipeline } from "node:stream/promises";
function qatorRaqami() {
let qoldiq = "";
let raqam = 0;
const chiqar = (oqim, q) => {
if (q !== "") oqim.push(`${++raqam}: ${q}`);
};
return new Transform({
readableObjectMode: true,
transform(bolak, _kod, tayyor) {
const qismlar = (qoldiq + bolak).split("\n");
qoldiq = qismlar.pop();
for (const q of qismlar) chiqar(this, q);
tayyor();
},
flush(tayyor) {
chiqar(this, qoldiq);
tayyor();
},
});
}
await pipeline(
Readable.from(["sana,k", "od\n\n2026-10-23,U", "SD"]),
qatorRaqami(),
async function (oqim) {
for await (const q of oqim) console.log(q);
},
);Konsolda:
1: sana,kod
2: 2026-10-23,USDBo'sh qator (\n\n orasidagi) q !== "" sharti bilan tashlandi, lekin raqam olmadi. Oxirgi 2026-10-23,USD flush da chiqdi. chiqar yordamchisi ikki joyda bir xil qoidani ishlatadi — transform va flush farq qilib qolmasligi uchun.
4-mashq: CLI qadami — matnga generatori
Bu dars holat 35 ning bir qismi; hali commit qilmang — commit katta fayl amaliyoti darsida. src/tarix.ts ga matnga(kurslar, format) async generatorini qo'shing. U Kurs obyektlari oqimini matn bo'laklariga aylantirsin: csv — sarlavha + qatorlar (csvQator), json — [ … ] bo'laklab (bo'sh oqimda ham yaroqli), jadval — sana bilan jadval qatori. Keyin oqimniYoz(matnga(...), ...) (oldingi dars) uni Readable.from bilan oqimga aylantiradi.
Yechim
Kanondagi kod:
export async function* matnga(
kurslar: AsyncIterable<Kurs>,
format: Format,
): AsyncGenerator<string> {
if (format === "csv") yield CSV_SARLAVHA + "\n";
if (format === "json") yield "[";
let birinchi = true;
for await (const k of kurslar) {
if (format === "csv") yield csvQator(k) + "\n";
else if (format === "json")
yield (birinchi ? "\n " : ",\n ") + JSON.stringify(k);
else yield `${k.sana} ${jadval([k], false)}\n`;
birinchi = false;
}
if (format === "json") yield birinchi ? "]\n" : "\n]\n";
}(Kanonda imzo bitta qatorda; telefonga sig'ishi uchun bo'lindi.) «Butun konveyer» bo'limidagi jsonMassiv bilan solishtiring: g'oya bir xil, faqat flush o'rnida sikldan keyingi yield. Kanon testi bo'sh oqimni tekshiradi: matnga(Readable.from([]), "json") → JSON.parse → [].
9. Real ishda
- Ma'lumot import/eksporti: CSV → bazaga, bazadan → Excel, log → JSON. Har birida o'rtada bitta-ikkita Transform turadi. Tayyor paketlar ham shu asosda:
csv-parse(CSV o'qish) Transform qaytaradi. - Kutubxonalar ichida:
zlib.createGzip(),crypto.createHash()oqim shakli — Node'ning o'z Transform'lari. Ularni ishlatganda siz bugungi_transform/_flushni bilvosita chaqirasiz. - Generatorlar tendensiyasi: yangi Node kodida
pipeline+ async generator tobora ko'p. Klassli Transform'larni asosan eski kodda va kutubxonalarda uchratasiz. - Intervyu savollari: "Transform va Duplex farqi?", "
_flushnima uchun kerak?", "objectModedahighWaterMarknimani sanaydi?".
Xulosa
- Readable:
read()ichidathis.push(bo'lak), oxiridathis.push(null). - Transform:
transform(bolak, _kod, tayyor)— har bo'lak;flush(tayyor)— oxirida bir marta. tayyor(null, x)— chiqar,tayyor()— tashla,tayyor(xato)— to'xtat; har yo'lda aynan bir marta.objectMode— baytlar o'rniga JS qiymatlari;highWaterMarkobyektlar sonini sanaydi.Readable.from(generator)vapipelineichidagi async generator — ko'p holatda eng qisqa va xavfsiz yo'l.
Keyingi dars: Async iteratorlar va Web Streams — oqimni for await bilan o'qish, Node va brauzer oqimlari orasida ko'prik (Readable.toWeb), TextDecoderStream va compose.
Manbalar
- Node.js hujjati: Implementing a Transform stream (v24).
- Node.js hujjati: stream.Readable.from.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!