Mundarija (31)
- Bu darsda
- 1. Nega bu kerak?
- 2. Papkalar: yaratish, o'qish, o'chirish
- 2.1 mkdir va recursive
- 2.2 readdir: papka ichida nima bor
- 2.3 rm: o'chirish va force
- 2.4 cp: nusxa
- 3. stat: fayl haqida ma'lumot
- 3.1 Hajm, tur va vaqt
- 3.2 Kesh yoshi
- 3.3 Kesh valyuta ichida
- 4. fs.glob: naqsh bo'yicha qidirish
- 4.1 Glob nima
- 4.2 Windows tuzog'i: teskari chiziq
- 4.3 valyuta kesh: ro'yxat va tozalash
- 5. FileHandle va flaglar
- 5.1 Faylni ochib ishlash
- 5.2 "wx": faqat yangi fayl
- 6. fs.watch: o'zgarishlarni kuzatish
- 6.1 Biz ishga tushirdik
- 6.2 fs.watch yoki chokidar
- 7. CLI qadami: kesh.ts (holat 18, 2-qism)
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): qaysi opsiya?
- 2-mashq (o'rta): papka hajmi
- 3-mashq (qiyin): eski fayllarni tozalash
- 4-mashq: CLI qadami
- 10. Real ishda
- Xulosa
- Manbalar
Node.js fs chuqur: papkalar, stat, glob, FileHandle va fs.watch
Qisqacha: Papka yaratish —
mkdir(yo'l, { recursive: true })(bor bo'lsa ham xato yo'q). Papka ichini o'qish —readdir(withFileTypes— fayl yoki papka ekanini ham beradi,recursive— ichma-ich). Fayl haqida ma'lumot —stat: hajm,mtimeMs(oxirgi o'zgarish vaqti). Naqsh bilan qidirish — Node 22+ dagifs.glob. O'chirish —rm({ recursive, force }), nusxa —cp. Faylni ochib qismlab ishlash —FileHandle(await usingbilan o'zi yopiladi). Kuzatish —fs.watch: tez, lekin hodisalar takrorlanadi va OS'ga bog'liq; jiddiy ishda — chokidar.
Bu darsda
- Ichma-ich papka yaratasiz, ularni aylanib chiqasiz va keraksizini xavfsiz o'chirasiz.
statbilan fayl hajmi va yoshini aniqlab, "kesh eskirdimi?" degan savolga javob berasiz.fs.globbilan naqsh bo'yicha fayl qidirasiz va Windows'dagi teskari chiziq tuzog'ini bilasiz.FileHandleva"wx"flagi nima uchun kerakligini tushunasiz.fs.watchhodisalarini haqiqiy chiqishda ko'rib, chokidar qachon kerakligini bilasiz.
Oldin bilishingiz kerak: fs: uch API shakli, Async iteratorlar, Promise kombinatorlari.
1. Nega bu kerak?
O'tgan darsda bitta faylni o'qish va yozishni o'rgandik. Haqiqiy dastur esa fayllar bilan emas, papkalar bilan yashaydi. valyuta kesh papkasida kurslarni saqlaydi. Ikki savol tug'iladi.
Birinchisi — kesh qachon eskiradi? CBU kursni kuniga bir marta yangilaydi. Bir soat oldin olingan javob — yaxshi, kechagi — yaroqsiz. Buni bilish uchun fayl qachon yozilgani kerak.
Ikkinchisi — kesh papkasida nima bor va uni qanday tozalash mumkin? Foydalanuvchi valyuta kesh deb yozsa — fayllar ro'yxatini ko'rsin, valyuta kesh tozala desa — hammasi o'chsin. Bu buyruqni har yaxshi CLI beradi: npm'da npm cache ls, npm cache clean bor.
O'xshatish: omborxona. Bitta qutini ochish (readFile) — o'tgan dars. Bugun — javonlar qurish (mkdir), ro'yxat olish (readdir), har qutidagi yorliqni o'qish (stat: og'irligi, kelgan sanasi), keraksizini chiqarib tashlash (rm) va eshik oldiga qorovul qo'yish (watch).
2. Papkalar: yaratish, o'qish, o'chirish
2.1 mkdir va recursive
valyuta kesh papkasi …\AppData\Local\valyuta\Cache — ichma-ich ikki papka. Yangi kompyuterda ularning hech biri yo'q. recursive: true — "yo'ldagi hamma yo'q papkani yarat":
import { mkdir, rm } from "node:fs/promises";
await rm("kesh", { recursive: true, force: true }); // toza boshlash
await mkdir("kesh/2026/10", { recursive: true });
console.log("yaratildi");
console.log(await mkdir("kesh/2026/10", { recursive: true }));
try {
await mkdir("kesh");
} catch (xato) {
console.log(xato.code);
}Konsolda:
yaratildi
undefined
EEXISTUch qoida ko'rinadi:
recursive: truebilan uchta papka birdaniga yaratildi.- Ikkinchi marta — xato yo'q, natija
undefined("hech narsa yaratilmadi"). Birinchi safar u yaratilgan birinchi papkaning to'liq yo'lini qaytaradi. recursivesiz bor papkani yaratish —EEXIST("allaqachon bor").
Shuning uchun faylga yozishdan oldin mkdir(papka, { recursive: true }) — odatiy qadam. U arzon va xavfsiz, "bor-yo'qligini tekshirish" kerak emas. valyuta ning atomik yozish funksiyasi ham shu qatordan boshlanadi.
2.2 readdir: papka ichida nima bor
readdir papkadagi nomlarni qaytaradi. withFileTypes: true bilan esa nomlar emas, Dirent obyektlari (directory entry — papka yozuvi) keladi. Ular "bu fayl yoki papka?" savoliga diskka qayta murojaat qilmay javob beradi:
import { mkdir, readdir, rm, writeFile } from "node:fs/promises";
await rm("kesh", { recursive: true, force: true });
await mkdir("kesh/2026/10", { recursive: true });
await writeFile("kesh/kurslar.json", "[]");
await writeFile("kesh/2026/10/23.json", "[1]");
const yozuvlar = await readdir("kesh", { withFileTypes: true });
for (const y of yozuvlar) {
console.log(y.name, y.isDirectory() ? "papka" : "fayl");
}Konsolda:
2026 papka
kurslar.json faylFaqat bitta daraja — 2026 ichiga kirmadi. Ichma-ich hammasi kerak bo'lsa — recursive: true. Natija OS'ga bog'liq, shuning uchun biz Windows'da ishga tushirgan chiqishni beramiz:
console.log(await readdir("kesh", { recursive: true }));[ '2026', 'kurslar.json', '2026\\10', '2026\\10\\23.json' ]'2026\\10' — bu 2026\10: JS satrida teskari chiziq ikki marta ko'rsatiladi. Windows yo'l ajratgichi — \. Linux va macOS'da xuddi shu kod 2026/10/23.json beradi (hujjatga ko'ra; biz Linux'da sinamadik). Yo'llar bilan to'g'ri ishlash — path va os darsida. Hozircha qoida: natijani / bilan bo'lib tahlil qilmang.
readdir qaytargan tartibga ham ishonmang — OS va fayl tizimiga bog'liq. Tartib kerak bo'lsa — o'zingiz saralang (valyuta shunday qiladi).
2.3 rm: o'chirish va force
rm — fayl yoki papkani o'chiradi. Ikki opsiya muhim:
| Opsiya | Ma'nosi |
|---|---|
recursive: true |
Papkani ichidagi hamma narsa bilan |
force: true |
Yo'q bo'lsa — xato bermaslik |
import { mkdir, rm } from "node:fs/promises";
await mkdir("vaqtinchalik", { recursive: true });
await rm("vaqtinchalik", { recursive: true });
try {
await rm("vaqtinchalik", { recursive: true });
} catch (xato) {
console.log("ikkinchi marta:", xato.code);
}
await rm("vaqtinchalik", { recursive: true, force: true });
console.log("force bilan: xato yo'q");Konsolda:
ikkinchi marta: ENOENT
force bilan: xato yo'qrecursive siz papkani o'chirmoqchi bo'lsangiz, Node aniq xato beradi. Biz ishga tushirdik:
ERR_FS_EISDIR Path is a directory: rm returned EISDIR (is a directory) keshDiqqat:
rm(…, { recursive: true, force: true })— terminaldagirm -rfning o'zi. U so'ramaydi, savatchaga tashlamaydi. Yo'l o'zgaruvchidan kelsa, avval uni tekshiring: bo'sh satr yoki"/"bo'lib qolsa, natija fojiali.valyutafaqat o'z kesh papkasi ichidagi fayllarni o'chiradi, papkaning o'zini emas.
2.4 cp: nusxa
cp (Node 22.3 dan barqaror) fayl yoki papkaning nusxasini oladi:
import { cp, mkdir, readdir, rm, writeFile } from "node:fs/promises";
await rm("asl", { recursive: true, force: true });
await rm("zaxira", { recursive: true, force: true });
await mkdir("asl/ichki", { recursive: true });
await writeFile("asl/a.json", "[]");
await writeFile("asl/ichki/b.json", "[]");
await cp("asl", "zaxira", { recursive: true });
const nomlar = await readdir("zaxira", { recursive: true });
console.log(nomlar.length);Konsolda:
3ichki papkasi va ikkita fayl — jami 3 yozuv. Papka uchun recursive: true shart.
Tekshirib ko'ring: Nega
valyutakesh papkasiniexistsSyncbilan tekshirib, keyinmkdirqilmaydi?
Javob
mkdir(…, { recursive: true }) papka bor bo'lsa ham xato bermaydi — tekshiruv ortiqcha. Ustiga, tekshiruv va yaratish orasida holat o'zgarishi mumkin (TOCTOU, fs: uch API shakli). Bitta amal — bitta murojaat, poyga yo'q.
3. stat: fayl haqida ma'lumot
3.1 Hajm, tur va vaqt
stat fayl tarkibini o'qimaydi — faqat uning "yorlig'ini" beradi:
import { stat, utimes, writeFile } from "node:fs/promises";
await writeFile("kurslar.json", '[{"Ccy":"USD"}]');
const vaqt = new Date("2026-10-23T05:00:00Z");
await utimes("kurslar.json", vaqt, vaqt); // vaqtni qo'lda qo'yamiz
const s = await stat("kurslar.json");
console.log(s.size);
console.log(s.isFile(), s.isDirectory());
console.log(s.mtime.toISOString());
console.log(s.mtimeMs);Konsolda:
15
true false
2026-10-23T05:00:00.000Z
1792731600000| Maydon | Ma'nosi |
|---|---|
size |
Hajm baytda |
isFile(), isDirectory() |
Turi |
mtime / mtimeMs |
Oxirgi o'zgartirilgan vaqt (modified) — Date / millisekund |
birthtime |
Yaratilgan vaqt (har fayl tizimida ham ishonchli emas) |
atime |
Oxirgi o'qilgan vaqt (ko'p tizimda o'chirilgan yoki kechikadi) |
mode |
Ruxsatlar (Linux/macOS) |
utimes — fayl vaqtlarini qo'lda o'rnatadi. Odatda u kerak emas, lekin testda juda foydali: "kesh 2 soat oldin yozilgan" holatini 2 soat kutmasdan yasash mumkin. valyuta testlari aynan shunday qiladi.
3.2 Kesh yoshi
Kesh yangimi — oddiy hisob: hozirgi vaqtdan mtimeMs ni ayiramiz. Bu toza funksiya, unda fs yo'q — saytda ham ishlaydi:
const MUDDAT_MS = 60 * 60 * 1000; // 1 soat
function keshHolati(mtimeMs, hozir) {
const yoshMs = hozir - mtimeMs;
const soat = (yoshMs / 3_600_000).toFixed(1);
const holat = yoshMs < MUDDAT_MS ? "yangi" : "eski";
return `${holat} (${soat} soat)`;
}
const yozilgan = Date.parse("2026-10-23T05:00:00Z");
console.log(keshHolati(yozilgan, Date.parse("2026-10-23T05:20:00Z")));
console.log(keshHolati(yozilgan, Date.parse("2026-10-23T07:00:00Z")));Konsolda:
yangi (0.3 soat)
eski (2.0 soat)hozir parametr sifatida berilgan — funksiya ichida Date.now() chaqirilmaydi. Shunda uni har qanday vaqt bilan sinash oson. Bu "vaqtni tashqaridan berish" usuli testlarda ko'p ishlatiladi (Vitest: mock va taymerlar).
Muddat nega 1 soat? CBU kunda bir marta yangilaydi. 1 soat — yangi kursni yarim kun kutib qolmaslik va API'ni har chaqiruvda bezovta qilmaslik o'rtasidagi kelishuv.
3.3 Kesh valyuta ichida
Kanon keshdanOqi funksiyasi stat va readFile ni parallel chaqiradi va yoshni hisoblaydi:
export async function keshdanOqi(papka: string, hozir = Date.now()): Promise<KeshYozuvi | undefined> {
const fayl = path.join(papka, "kurslar.json");
try {
const [s, matn] = await Promise.all([stat(fayl), readFile(fayl, "utf8")]);
const xom: unknown = JSON.parse(matn);
return { kurslar: royxatniOqi(xom), yoshMs: hozir - s.mtimeMs };
} catch (xato) {
// fayl yo'q yoki buzilgan — kesh yo'qdek ishlaymiz
if (xato instanceof SyntaxError || xato instanceof TypeError) return undefined;
if (xato instanceof Error && "code" in xato && xato.code === "ENOENT") return undefined;
throw xato;
}
}Kesh — yordamchi ma'lumot, haqiqat manbai emas. Shuning uchun buzilgan JSON (SyntaxError) yoki noto'g'ri tuzilma (TypeError — royxatniOqi tashlaydi) — "kesh yo'q" deb qabul qilinadi: keyingi muvaffaqiyatli so'rov uni qayta yozadi. Sozlama faylida esa (o'tgan dars) buzilgan JSON — xato edi. Farq: sozlamani odam yozadi va uning xatosini ko'rishi kerak; keshni dastur yozadi va o'zi tuzatadi.
Kesh qanday ishlatilishini rasmda ko'ring. Romb — savol, to'rtburchak — harakat:
flowchart TD
A["valyuta kurs USD"] --> B{"kesh yoshi < 1 soat?"}
B -->|ha| K["(kesh)"]
B -->|yo'q| C{"CBU API javob berdimi?"}
C -->|ha| D["keshga yozish"] --> E["(api)"]
C -->|yo'q| F{"eski kesh bormi?"}
F -->|ha| G["(eski-kesh) + ogohlantirish"]
F -->|yo'q| H["(fixture) + ogohlantirish"]Qavs ichidagi so'z — jadval sarlavhasida chiqadigan manba nomi. Foydalanuvchi har doim kurs qayerdan kelganini ko'radi.
Tekshirib ko'ring: Kesh fayli 3 soat oldin yozilgan, internet yo'q.
valyuta kurs USDnima ko'rsatadi?
Javob
Kesh eski (3 > 1 soat) — API'ga so'rov ketadi. API javob bermaydi — eski kesh bor, u ishlatiladi: sarlavhada (eski-kesh) va stderr'da Ogohlantirish: API ishlamadi (…) — 3.0 soat oldingi kesh ishlatildi. Natija bor, lekin foydalanuvchi uning eskiligini biladi.
4. fs.glob: naqsh bo'yicha qidirish
4.1 Glob nima
Glob — fayl nomlari uchun soddalashtirilgan naqsh. *.json — ".json bilan tugaydigan hamma nom", ** — "istalgan chuqurlikdagi papkalar". Terminalda ls *.json yozganingizda aynan shu ishlaydi. ESLint, Prettier, node --test "test/**/*.test.ts" — hammasi glob qabul qiladi.
Node 22 dan fs.glob o'rnatilgan, Node 24 da barqaror va ogohlantirishsiz. Ilgari buning uchun glob yoki fast-glob paketi o'rnatilardi.
fs.glob — async iterator qaytaradi (async iteratorlar): natijalar topilgan sayin for await ga keladi.
import { glob, mkdir, rm, writeFile } from "node:fs/promises";
await rm("kesh", { recursive: true, force: true });
await mkdir("kesh/2026", { recursive: true });
await writeFile("kesh/kurslar.json", "[]");
await writeFile("kesh/sozlama.txt", "");
await writeFile("kesh/2026/23.json", "[]");
for await (const nom of glob("*.json", { cwd: "kesh" })) {
console.log("topildi:", nom);
}
const hammasi = await Array.fromAsync(
glob("**/*.json", { cwd: "kesh" }),
);
console.log(hammasi.length);Konsolda:
topildi: kurslar.json
2*.json — faqat yuqori daraja, **/*.json — ichma-ich ham. Array.fromAsync — async iteratorni oddiy massivga yig'adi (ES2024).
4.2 Windows tuzog'i: teskari chiziq
Ichki papkadagi faylni nima deb qaytaradi? Biz Windows'da ishga tushirdik:
for await (const nom of glob("**/*.json", { cwd: "kesh" })) {
console.log(nom);
}kurslar.json
2026\23.jsonKanon ham shuni topdi: fs.glob Windows'da yo'llarni \ bilan qaytaradi. Naqsh esa har doim / bilan yoziladi (**/*.json), \ naqshda maxsus belgi. Ya'ni kirish — /, chiqish — OS ajratgichi. Natijani path.join bilan ishlating, qo'lda "/" bilan birlashtirmang.
4.3 valyuta kesh: ro'yxat va tozalash
Kanon kesh.ts dagi ikki funksiya:
export async function keshRoyxati(papka: string): Promise<KeshFayli[]> {
const natija: KeshFayli[] = [];
for await (const nom of glob("*", { cwd: papka })) {
const s = await stat(path.join(papka, nom));
if (s.isFile()) natija.push({ nom, hajm: s.size, ozgargan: s.mtime });
}
return natija.sort((a, b) => a.nom.localeCompare(b.nom));
}
export async function keshniTozala(papka: string): Promise<number> {
const fayllar = await keshRoyxati(papka);
await Promise.all(fayllar.map((f) => rm(path.join(papka, f.nom), { force: true })));
return fayllar.length;
}Uch detalga qarang. isFile() — papkalar ro'yxatga kirmaydi. sort — tartib OS'ga bog'liq bo'lmasin. force: true — fayl ro'yxat olingandan keyin boshqa jarayon tomonidan o'chirilgan bo'lsa ham xato yo'q.
Kanonning jonli natijasi (haqiqiy CBU, holat 18):
$ valyuta kesh
kurslar.json 25533 bayt 2026-10-23T05:19:17.109ZKesh bo'sh bo'lsa:
$ valyuta kesh
Kesh bo'sh (C:\Users\…\AppData\Local\valyuta\Cache)5. FileHandle va flaglar
5.1 Faylni ochib ishlash
readFile faylni ochadi, hammasini o'qiydi va yopadi — bitta chaqiruvda. Ba'zan faylni ochiq ushlab, u bilan bir necha amal qilish kerak: faqat boshini o'qish, stat, bo'lak-bo'lak yozish. Buning uchun open FileHandle (fayl dastasi) qaytaradi. O'xshatish: kutubxonadagi kitobni olib ketish. readFile — o'qish zalida bir o'tirishda o'qib qaytarish; FileHandle — kitob qo'lingizda, xohlagan sahifani ochasiz, lekin oxirida qaytarish shart.
Qaytarmaslik — ya'ni close() ni unutish — xato. OS bir jarayonga ochiq fayllar sonini cheklaydi. Node 24 da await using sintaksisi faylni blokdan chiqishda o'zi yopadi:
import { open, writeFile } from "node:fs/promises";
await writeFile("kurslar.json", '[{"Ccy":"USD","Rate":"11903.15"}]');
{
await using fayl = await open("kurslar.json", "r");
const { size } = await fayl.stat();
const bosh = Buffer.alloc(8);
await fayl.read(bosh, 0, 8, 0);
console.log(size, bosh.toString());
} // shu yerda fayl.close() avtomatik
console.log("yopildi");Konsolda:
33 [{"Ccy":
yopildiawait using — resurslarni aniq boshqarish imkoniyati: blok tugaganda — xato bilan tugasa ham — obyektning Symbol.asyncDispose metodi chaqiriladi. FileHandle'da u close() ni bajaradi (Node hujjati: v24.2 dan barqaror, oldin tajribaviy edi). Eski kodda xuddi shu ish try { … } finally { await fayl.close(); } bilan yozilgan — uni ham ko'rasiz. Buffer — Buffer darsida.
5.2 "wx": faqat yangi fayl
"wx" flagi (w — yozish, x — exclusive, "faqat menga") faylni faqat u yo'q bo'lsa yaratadi:
import { rm, writeFile } from "node:fs/promises";
await rm("qulf", { force: true });
await writeFile("qulf", String(process.pid), { flag: "wx" });
try {
await writeFile("qulf", "boshqa jarayon", { flag: "wx" });
} catch (xato) {
console.log(xato.code);
}
await rm("qulf");Konsolda:
EEXIST"Bormi?" tekshiruvi va yaratish — OS darajasida bitta amal. Ikki jarayon bir vaqtda urinsa ham, faqat bittasi muvaffaqiyatli bo'ladi. Shuning uchun "wx" oddiy qulf fayl (lock file) uchun ishlatiladi: "bu ishni hozir boshqa nusxa bajaryapti". Xavfsiz yozishning boshqa usullari — keyingi darsda.
Tekshirib ko'ring:
existsSync("qulf")bilan tekshirib, keyin oddiywriteFileqilsak,"wx"bilan bir xil bo'ladimi?
Javob
Yo'q. Ikki jarayon bir vaqtda existsSync → false ni olishi mumkin va ikkalasi ham yozadi — ikkalasi o'zini "qulf egasi" deb biladi. "wx" da tekshiruv va yaratish ajralmas: ikkinchisi albatta EEXIST oladi.
6. fs.watch: o'zgarishlarni kuzatish
6.1 Biz ishga tushirdik
fs.watch papka yoki faylni kuzatadi va o'zgarganda callback chaqiradi. --watch rejimi, Vite dev serveri, tsc --watch — hammasi shunga o'xshash mexanizmda ishlaydi. Tajriba: papkani kuzatamiz, faylni yaratamiz, qayta yozamiz, nomini o'zgartiramiz va o'chiramiz. Har amal orasida 100 ms:
import { watch } from "node:fs";
import { mkdir, rename, rm, writeFile } from "node:fs/promises";
import { setTimeout as kut } from "node:timers/promises";
await rm("kuzat", { recursive: true, force: true });
await mkdir("kuzat");
const hodisalar = [];
const w = watch("kuzat", (tur, nom) => {
hodisalar.push(`${tur} ${nom}`);
});
await kut(100);
await writeFile("kuzat/config.json", '{"a":1}');
await kut(100);
await writeFile("kuzat/config.json", '{"a":2}');
await kut(100);
await rename("kuzat/config.json", "kuzat/eski.json");
await kut(100);
await rm("kuzat/eski.json");
await kut(200);
w.close();
console.log(hodisalar.join("\n"));Windows 11, Node 24.21.0, uch marta ishga tushirdik — uchalasida bir xil:
rename config.json
change config.json
change config.json
change config.json
rename config.json
rename eski.json
rename eski.jsonBu chiqishni o'qish oson emas — va aynan shu darsning saboqi:
- Faqat ikki xil hodisa:
rename(paydo bo'ldi yoki yo'qoldi) vachange(ichi o'zgardi). "Yaratildi", "o'chirildi" degan aniq so'z yo'q — buni o'zingizstatbilan aniqlaysiz. - Bitta
writeFile— bir nechta hodisa. Ikkinchi yozish ikki martachangeberdi. Dastur har hodisada qayta yuklansa, bir o'zgarishga ikki marta ishlaydi. - Nomini o'zgartirish — ikki
rename: eski nom va yangi nom. - Hodisalar soni va turi OS'ga bog'liq: Windows, Linux (inotify) va macOS (FSEvents) har xil beradi. Hujjat bu API "100 % izchil emas" deb ochiq aytadi.
Takrorlanishga qarshi oddiy usul — debounce (debounce va throttle): oxirgi hodisadan keyin 100–200 ms jimlik bo'lsa, shunda ishlash.
6.2 fs.watch yoki chokidar
fs.watch |
chokidar | |
|---|---|---|
| O'rnatish | o'rnatilgan | npm i chokidar (2026-10-23 holatiga 6.0.0) |
| Hodisalar | rename/change, takrorlanadi |
add, change, unlink, addDir — aniq |
| Yozish tugashini kutish | yo'q | awaitWriteFinish opsiyasi |
| Ichma-ich papkalar | recursive: true |
sukut bo'yicha |
Kichik ichki skript uchun fs.watch + debounce yetarli. Foydalanuvchi fayllarini kuzatadigan jiddiy vosita uchun — chokidar (Vite va ko'p asboblar ichida shunga o'xshash yechim bor). Biz chokidar'ni kanonda o'rnatmadik — versiya npm view dan. Yana bir variant — fs.watchFile: u fayl stat ini vaqti-vaqti bilan so'raydi (polling). Sekin va CPU yeydi, lekin tarmoq disklarida ishlaydi.
valyuta ga kuzatish kerakmi? Yo'q: CLI bir marta ishlaydi va tugaydi, kuzatadigan hech narsa yo'q. Dars kodi valyuta ga kirmaydi.
7. CLI qadami: kesh.ts (holat 18, 2-qism)
Holat 18 ning ikkinchi qismi — src/kesh.ts va kesh buyrug'i. Hali commit qilmang — holat 18 path va os darsida commit qilinadi.
src/kesh.ts:MUDDAT_MS,keshdanOqi(yuqoridagi kod),keshRoyxati,keshniTozala.keshgaYozhozircha oddiywriteFilebo'lsin — keyingi darsda uni atomik qilamiz.cli.ts: yangi buyruqkesh(ro'yxat) vakesh tozala(o'chirish, nechta fayl o'chganini ko'rsatadi).manba.ts: kesh yangi bo'lsa — API'ga bormaslik, eski bo'lsa — API, API yiqilsa — eski kesh, u ham bo'lmasa — fixture (yuqoridagi rasm).
Kanon testlarida utimes kesh faylini "2 soat oldin yozilgan" qiladi va soxta API 500 qaytaradi. Kutilgan natija — manba: "eski-kesh". Kanon jonli o'lchovi: birinchi valyuta kurs USD — (api), 399 ms; ikkinchisi — (kesh), 138 ms. Kesh vaqtni uch barobarga yaqin qisqartirdi va CBU'ga ortiqcha so'rov ketmadi.
8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
ENOENT yozishda |
Papka hali yo'q | Avval mkdir(papka, { recursive: true }) |
EEXIST mkdir da |
recursive yo'q |
{ recursive: true } |
ERR_FS_EISDIR rm da |
Papka recursive siz |
{ recursive: true } |
| Natija har kompyuterda boshqa tartibda | readdir/glob tartibi kafolatlanmagan |
sort |
2026\23.json kutilmagan joyda |
Windows ajratgichi | path.join, / bilan bo'lmang |
EMFILE: too many open files |
FileHandle yopilmagan |
await using yoki finally |
| Kuzatuvchi bir o'zgarishga ikki marta ishladi | fs.watch takroriy hodisalari |
Debounce yoki chokidar |
9. Mashqlar
1-mashq (oson): qaysi opsiya?
- Bor papkada
mkdirxato bermasligi uchun:{ : true } rmyo'q faylda xato bermasligi uchun:{ : true }- Oxirgi o'zgarish vaqti (millisekund) —
statdagi maydon: - Fayl faqat yo'q bo'lsa yaratadigan flag:
readdirfayl yoki papka ekanini ham bersin:{ : true }
Yechim
- recursive — yo'ldagi hamma papka, bor bo'lsa — jim.
- force —
rm -fkabi. - mtimeMs — kesh yoshi uchun.
- wx — qulf fayl uchun.
- withFileTypes —
Direntobyektlari.
2-mashq (o'rta): papka hajmi
papkaHajmi(papka) funksiyasini yozing: papka ichidagi (ichma-ich ham) hamma fayllar hajmining yig'indisini qaytarsin. readdir(…, { recursive: true, withFileTypes: true }) va stat ishlating. Dirent'da fayl joylashgan papka — parentPath maydonida. Sinash uchun uchta fayl yarating: 3, 5 va 10 bayt.
Yechim
import {
mkdir, readdir, rm, stat, writeFile,
} from "node:fs/promises";
import path from "node:path";
async function hajmi(yozuv) {
const s = await stat(path.join(yozuv.parentPath, yozuv.name));
return s.size;
}
async function papkaHajmi(papka) {
const yozuvlar = await readdir(papka, {
recursive: true,
withFileTypes: true,
});
const fayllar = yozuvlar.filter((y) => y.isFile());
const hajmlar = await Promise.all(fayllar.map(hajmi));
return hajmlar.reduce((a, b) => a + b, 0);
}
await rm("ombor", { recursive: true, force: true });
await mkdir("ombor/ichki/chuqur", { recursive: true });
await writeFile("ombor/a.txt", "abc");
await writeFile("ombor/ichki/b.txt", "12345");
await writeFile("ombor/ichki/chuqur/c.txt", "0123456789");
console.log(await papkaHajmi("ombor"));Konsolda:
18Promise.all — hamma stat parallel. Juda katta papkada (o'n minglab fayl) bu bir vaqtda juda ko'p fayl ochishga olib kelishi mumkin — u holda bo'laklab ishlash kerak (xavfsiz fayl operatsiyalari, EMFILE). Papkalar hajmi (isFile() filtri) hisobga olinmadi — ularning size i OS'ga bog'liq.
3-mashq (qiyin): eski fayllarni tozalash
eskilarniTozala(papka, maksimalYoshMs, hozir) funksiyasini yozing: papkadagi (faqat yuqori daraja) *.json fayllardan yoshi maksimalYoshMs dan katta bo'lganlarini o'chirsin va o'chirilgan nomlarni alifbo tartibida qaytarsin. fs.glob va stat ishlating; hozir — parametr. Sinash uchun uch fayl yarating va utimes bilan ularga 1, 5 va 30 soatlik yosh bering. 24 soatdan eskilari o'chsin. Bitta .txt fayl ham qo'ying — u eski bo'lsa ham qolsin.
Yechim
import {
glob, mkdir, readdir, rm, stat, utimes, writeFile,
} from "node:fs/promises";
import path from "node:path";
const SOAT = 3_600_000;
async function eskilarniTozala(papka, maksimalYoshMs, hozir) {
const ochirildi = [];
for await (const nom of glob("*.json", { cwd: papka })) {
const fayl = path.join(papka, nom);
const s = await stat(fayl);
if (hozir - s.mtimeMs > maksimalYoshMs) {
await rm(fayl, { force: true });
ochirildi.push(nom);
}
}
return ochirildi.sort();
}
const hozir = Date.parse("2026-10-23T12:00:00Z");
await rm("kesh", { recursive: true, force: true });
await mkdir("kesh");
const yoshlar = {
"a.json": 1,
"b.json": 5,
"c.json": 30,
"d.txt": 30,
};
for (const [nom, soat] of Object.entries(yoshlar)) {
await writeFile(path.join("kesh", nom), "[]");
const vaqt = new Date(hozir - soat * SOAT);
await utimes(path.join("kesh", nom), vaqt, vaqt);
}
console.log(await eskilarniTozala("kesh", 24 * SOAT, hozir));
console.log((await readdir("kesh")).sort());Konsolda:
[ 'c.json' ]
[ 'a.json', 'b.json', 'd.txt' ]d.txt 30 soatlik bo'lsa ham qoldi — naqsh *.json. Aynan shu funksiya valyuta ga "eski sanali keshlarni tozalash" sifatida qo'shilishi mumkin edi; kanonda esa oddiyroq kesh tozala (hammasini) tanlangan.
4-mashq: CLI qadami
«CLI qadami» bo'limidagi uch qadamni bajaring. valyuta kesh (bo'sh) → Kesh bo'sh (…); valyuta kurs USD (tarmoq bilan) → (api); yana bir marta → (kesh); valyuta kesh → bitta fayl; valyuta kesh tozala → O'chirildi: 1 fayl (…). Commit qilmang.
Yechim
Tarmoq bo'lmasa, birinchi chaqiruv (fixture) bo'ladi va kesh yozilmaydi — bu to'g'ri xulq: fixture keshga ko'chirilmaydi, aks holda soxta "yangi" kesh paydo bo'lardi. Kesh yozilganini tekshirish uchun internetni yoqib qayta urining. Hozircha kesh process.cwd() dagi vaqtinchalik papkada bo'lishi mumkin — to'g'ri OS papkasi path va os darsida.
10. Real ishda
- Build vositalari — Vite, tsc, ESLint papkalarni
readdir/glob bilan aylanib chiqadi,statbilan o'zgargan fayllarni aniqlaydi (inkremental build — faqat o'zgarganlarini qayta ishlash). - Kesh papkalari — npm (
~/.npm/_cacache), Next.js (.next/cache), Playwright brauzerlari. Hammasida "eski fayllarni tozalash" buyrug'i bor. - Fayl yuklash serveri — yuklangan fayllar uchun
mkdir, vaqtinchalik fayllarnirm, eski fayllarnistatbilan tozalash cron vazifasi. Serverni keyingi qismda yozamiz. - Intervyu savoli: "
fs.watchnega ishonchsiz va nima ishlatasiz?", "Papka hajmini qanday hisoblaysiz?".
Xulosa
mkdir({ recursive: true })— bor bo'lsa jim;rm({ recursive, force })—rm -rf, ehtiyot bo'ling;cp({ recursive })— nusxa.readdir—withFileTypes(Dirent),recursive; tartib va ajratgich OS'ga bog'liq — saralang,path.joinishlating.stat—size,mtimeMs; kesh yoshi =hozir − mtimeMs; testdautimes.fs.glob(Node 22+) — async iterator; naqsh/bilan, Windows natijasi\bilan.FileHandle—await usingbilan o'zi yopiladi;"wx"— atomik "faqat yangi fayl", qulf uchun.fs.watchhodisalari takrorlanadi va OS'ga bog'liq — debounce yoki chokidar.
Keyingi dars: Xavfsiz fayl operatsiyalari — yozish o'rtasida dastur o'lsa nima bo'ladi: oddiy yozish 20 dan 17 marta buzilgan fayl qoldirdi, atomik yozish — 0.
Manbalar
- Node.js 24 hujjati — fs: Promises API (
mkdir,readdir,stat,rm,cp,glob,FileHandle) - Node.js 24 hujjati — fs.watch: Caveats (platformalar farqi, takroriy hodisalar)
- MDN — Explicit resource management (
using,await using) - chokidar — https://github.com/paulmillr/chokidar (npm: 6.0.0, 2026-10-23 holatiga)
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!