IlmHamroh
JavaScript Full-stack/24-qism. Node.js15/55-dars20 daqiqa
Mundarija (31)

Node.js fs moduli: uch API shakli — sync, callback va promise

Qisqacha: node:fs dagi deyarli har amal uch shaklda bor: sync (readFileSync — natija kelguncha butun dastur kutadi), callback (readFile(fayl, (xato, matn) => …) — eski uslub) va promise (node:fs/promises dan await readFile(…)). Yangi kodda — promise shakli. Encoding bermasangiz Buffer (baytlar) qaytadi, "utf8" bersangiz — matn. Fayl yo'q bo'lsa xato code — "ENOENT": uni try/catch da ushlab, "yo'q bo'lsa sukut qiymat" qilish — eng ko'p uchraydigan naqsh.

Bu darsda

  • Bitta amalni uch shaklda yozib, ular qachon to'g'ri ekanini tushuntira olasiz.
  • readFile, writeFile, appendFile bilan fayl o'qiysiz va yozasiz; encoding va Buffer farqini bilasiz.
  • Xato obyektidagi code, syscall, errno ni o'qib, ENOENT ni boshqa xatolardan ajratasiz.
  • valyuta uchun ixtiyoriy config.json ni xavfsiz o'qiydigan kodni tushunasiz (holat 18 ning birinchi qismi).

Oldin bilishingiz kerak: Blocking va non-blocking I/O, libuv thread pool, ES Modules Node'da, async/await, try/catch.

1. Nega bu kerak?

Hozircha valyuta har chaqirilganda CBU'dan kursni qaytadan so'raydi va hech narsani eslab qolmaydi. Foydalanuvchi esa ikki narsani xohlaydi. Birinchisi — kurs tez chiqsin, ya'ni oxirgi javob diskda saqlansin (kesh). Ikkinchisi — valyuta kurs deb yozganda har safar USD EUR RUB ni terib o'tirmasin, sevimli valyutalar sozlama faylida tursin.

Ikkalasi ham bitta ko'nikmaga tayanadi: diskdagi faylni o'qish va yozish. Brauzerda buni deyarli qila olmasdingiz — sayt foydalanuvchi diskiga tegolmaydi. Node'da esa bu oddiy ish, uni fs moduli (file system — fayl tizimi) bajaradi.

Lekin fs ni birinchi ochganingizda chalkashib qolasiz: readFile, readFileSync, fs.promises.readFile — uchtasi ham bor. Qaysi biri to'g'ri? Bu darsning javobi: uchalasi ham bitta ishni qiladi, farqi — kutish usulida.

O'xshatish: choyxonada choy buyurtma qilish. Sync — choyxonachi choy damlanguncha sizning stolingiz yonida turadi, boshqa mijozga qaramaydi. Callback — "tayyor bo'lsa, chaqiraman" deydi va keyin chaqiradi. Promise — sizga raqamli chek beradi: chek bilan keyin choyni olasiz, xohlasangiz bir nechta chekni birdan kutasiz.

2. Uch shakl yonma-yon

2.1 Bitta fayl, uch xil o'qish

Avval faylni yaratamiz, keyin uni uch usulda o'qiymiz. Bu blok to'liq Node'da ishlaydi — saytdagi natija oynasi brauzer, unda fs yo'q. O'z kompyuteringizda .mjs fayl qilib ishga tushiring.

js
import fs from "node:fs";
import { readFile } from "node:fs/promises";

fs.writeFileSync("config.json", '{"valyutalar": ["USD", "EUR"]}');

// 1) sync: natija shu qatorning o'zida
const a = fs.readFileSync("config.json", "utf8");
console.log("sync:", a.length);

// 2) promise: await bilan
const c = await readFile("config.json", "utf8");
console.log("promise:", c.length);

Konsolda:

text
sync: 30
promise: 30

Ikkalasi ham 30 belgilik matn qaytardi. Farq ko'rinmaydi, chunki fayl kichik va boshqa ish yo'q. Farq kutish paytida nima bo'lishida.

Callback shaklini alohida ko'ramiz — u natijani darhol emas, keyinroq beradi:

js
import fs from "node:fs";

fs.writeFileSync("config.json", '{"valyutalar": ["USD", "EUR"]}');

fs.readFile("config.json", "utf8", (xato, matn) => {
  if (xato) {
    console.log("xato:", xato.code);
    return;
  }
  console.log("callback:", matn.length);
});
console.log("readFile chaqirildi, davom etamiz");

Konsolda:

text
readFile chaqirildi, davom etamiz
callback: 30

Qatorlar tartibiga qarang. readFile faylni o'qishni boshlaydi va darhol qaytadi. Kod keyingi qatorga o'tadi. Fayl o'qilgach, Node callback'ni event loop orqali chaqiradi (event loop fazalari).

2.2 Ular ichida nima bo'ladi

Qaysi thread ishlayotganiga qarang. Sync shaklda JS thread'ining o'zi diskni kutadi. Callback va promise shakllarida esa kutishni libuv thread pool'i oladi (thread pool darsi):

sequenceDiagram
  participant JS as JS thread
  participant P as libuv thread pool
  participant D as Disk
  JS->>D: readFileSync (JS kutib turadi)
  D-->>JS: baytlar
  JS->>P: readFile / await readFile
  Note over JS: boshqa ishlarni bajaradi
  P->>D: o'qish
  D-->>P: baytlar
  P-->>JS: callback yoki Promise bajarildi

Birinchi juftlikda JS thread hech narsa qilmaydi — faqat kutadi. Ikkinchisida u bo'sh, boshqa so'rovlarga xizmat qila oladi.

2.3 Qaysi birini qachon

Shakl Import Qachon to'g'ri
promise import { readFile } from "node:fs/promises" Yangi kodning deyarli hammasi
sync import { readFileSync } from "node:fs" Dastur boshida bir marta, kichik skript, CLI ishga tushishi
callback import { readFile } from "node:fs" Eski kod; eng tez (Promise obyekti yo'q), lekin noqulay

Sync qachon zararli? Server ko'p foydalanuvchiga xizmat qilganda. Bitta so'rovda readFileSync 50 ms kutsa, shu 50 ms davomida hamma foydalanuvchi kutadi (event loop'ni bloklash). CLI'da esa foydalanuvchi bitta, u baribir natijani kutadi. Shuning uchun ba'zi CLI'lar ishga tushishda sync ishlatadi — bu xato emas, ongli tanlov. valyuta esa promise shaklini ishlatadi: kod bir xil uslubda bo'ladi va keyinchalik serverda ham qayta ishlatish mumkin.

node:fs/promises va fs.promises — bitta narsa, ikki xil yo'l:

js
import fs from "node:fs";
import * as fsp from "node:fs/promises";

console.log(fs.promises.readFile === fsp.readFile); // true

Tekshirib ko'ring: CLI dastur ishga tushganda bitta kichik sozlama faylini readFileSync bilan o'qiydi. Bu event loop'ni bloklash xatosimi?

Javob

Odatda yo'q. Event loop'ni bloklash zararli, chunki shu paytda boshqa ishlar (so'rovlar, taymerlar) kutib qoladi. CLI ishga tushayotganda boshqa ish yo'q — foydalanuvchi baribir natijani kutadi. Serverda esa har so'rov ichidagi sync o'qish hamma mijozni to'xtatadi.

3. O'qish: encoding va Buffer

3.1 Encoding bermasangiz

readFile ga encoding bermasangiz, u matn emas, Buffer qaytaradi:

js
import { readFile, writeFile } from "node:fs/promises";

await writeFile("narx.txt", "so'm");
const xom = await readFile("narx.txt");
console.log(xom);
console.log(xom.length);
console.log(xom.toString("utf8"));

Konsolda:

text
<Buffer 73 6f 27 6d>
4
so'm

Buffer — xotiradagi xom baytlar ketma-ketligi. 73 — s harfi, 6f — o, 27 — apostrof, 6d — m. Har bayt o'n oltilik (hex) sanoq tizimida ko'rsatilgan. Buffer'ni chuqur Buffer darsida o'rganamiz. Hozir bitta qoidani eslang: matn kerak bo'lsa — encoding bering.

js
const matn = await readFile("config.json", "utf8");
// yoki obyekt shaklida:
const matn2 = await readFile("config.json", { encoding: "utf8" });

3.2 Encoding nima va qaysilari bor

Encoding (kodlash) — baytlarni harflarga aylantirish qoidasi. O'xshatish: Morze alifbosi. Bir xil "nuqta-tire" ketma-ketligini to'g'ri jadval bilan o'qisangiz — so'z, noto'g'risi bilan — bema'ni harflar chiqadi.

Encoding Nima uchun
"utf8" Deyarli hamma matn (JSON, CSV, o'zbek, rus harflari)
"base64" Binar ma'lumotni matn sifatida uzatish (rasm, kalit)
"hex" Baytlarni ko'rish, hash'lar
"latin1" Eski fayllar; har belgi — bitta bayt

Ko'p baytli harfda noto'g'ri encoding qanday buziladi — Kirill harfi bilan ko'ramiz:

js
import { readFile, writeFile } from "node:fs/promises";

await writeFile("rubl.txt", "рубль");
const togri = await readFile("rubl.txt", "utf8");
const notogri = await readFile("rubl.txt", "latin1");
console.log(togri, togri.length);
console.log(notogri.length, notogri === togri);
console.log((await readFile("rubl.txt")).length);

Konsolda:

text
рубль 5
10 false
10

5 harf — 10 bayt: UTF-8 da har Kirill harfi 2 bayt. latin1 har baytni alohida harf deb o'qidi: 10 ta "harf" chiqdi, ekranda ular bema'ni belgilar ("krakozyabra") bo'lib ko'rinadi. CBU javobidagi CcyNm_RU maydonini noto'g'ri encoding bilan o'qisangiz, aynan shunday ko'rinadi.

3.3 JSON faylni o'qish

JSON fayl — matn. O'qish ikki qadam: matnni olish, keyin JSON.parse:

js
import { readFile, writeFile } from "node:fs/promises";

await writeFile("config.json", '{"valyutalar": ["usd", "eur"]}');
const sozlama = JSON.parse(await readFile("config.json", "utf8"));
console.log(sozlama.valyutalar.map((v) => v.toUpperCase()));

Konsolda:

text
[ 'USD', 'EUR' ]

ES Modules darsida yana bir yo'lni ko'rgansiz: import paket from "./package.json" with { type: "json" }. Farqi: import faylni dastur ishga tushganda bir marta o'qiydi va natijani keshlaydi. Fayl yo'q bo'lsa — dastur umuman ishga tushmaydi. Foydalanuvchi o'zgartiradigan, bo'lishi ham, bo'lmasligi ham mumkin bo'lgan fayl uchun readFile kerak.

Tekshirib ko'ring: "so'm" 4 bayt edi. "o'zbek" necha bayt bo'ladi? "Oʻzbek" (maxsus ʻ belgisi bilan) chi?

Javob

"o'zbek" — 6 bayt: hamma belgi lotin va oddiy apostrof, har biri 1 bayt. "Oʻzbek" — 7 bayt: ʻ (U+02BB) belgisi UTF-8 da 2 bayt. Shuning uchun kursda oddiy apostrof ishlatiladi — u hamma joyda bir xil va bitta bayt.

4. Yozish: writeFile va appendFile

4.1 writeFile faylni almashtiradi

writeFile — fayl yo'q bo'lsa yaratadi, bor bo'lsa butunlay almashtiradi:

js
import { readFile, writeFile } from "node:fs/promises";

await writeFile("kurs.txt", "USD 11903.15\nEUR 13329.15\n");
await writeFile("kurs.txt", "RUB 146.75\n");
console.log(await readFile("kurs.txt", "utf8"));

Konsolda:

text
RUB 146.75

Birinchi yozuv yo'qoldi. Bu tuzoq emas, aynan shunday ishlaydi — lekin "nega USD yo'qoldi?" degan savol ko'p beriladi.

4.2 appendFile oxiriga qo'shadi

Log yoki tarix fayli uchun — appendFile:

js
import { appendFile, readFile, writeFile } from "node:fs/promises";

await writeFile("tarix.log", ""); // har safar toza boshlaymiz
await appendFile("tarix.log", "10:00 kurs USD\n");
await appendFile("tarix.log", "10:05 100 USD\n");
console.log(await readFile("tarix.log", "utf8"));

Konsolda:

text
10:00 kurs USD
10:05 100 USD

Birinchi qatordagi writeFile ga e'tibor bering. Usiz dasturni ikkinchi marta ishga tushirsangiz, fayl oldingi qatorlarni ham saqlab qoladi va to'rt qator chiqadi. appendFile — "qo'sh", u hech qachon tozalamaydi.

4.3 Yozish opsiyalari

writeFile uchinchi parametr sifatida obyekt oladi:

js
await writeFile("kesh.json", matn, {
  encoding: "utf8", // sukut ham shu
  mode: 0o600,      // Linux/macOS: faqat egasi o'qiy va yoza oladi
  flag: "w",        // sukut: yarat yoki almashtir
});
Flag Ma'nosi
"w" yozish (sukut): yo'q bo'lsa yaratadi, bor bo'lsa tozalaydi
"a" qo'shish — appendFile ning o'zi
"wx" faqat yangi fayl: bor bo'lsa EEXIST xatosi

mode: 0o600 — Linux va macOS uchun ruxsat: "faqat fayl egasi". valyuta kesh faylini shu bilan yozadi. Windows bu raqamni deyarli e'tiborsiz qoldiradi — u yerda ruxsatlar boshqa tizimda. "wx" va ruxsatlarni fs chuqur darsida davom ettiramiz.

Tekshirib ko'ring: Har kurs so'rovini kurslar.log ga yozib boramiz. Qaysi biri kerak — writeFile yoki appendFile?

Javob

appendFile — har yangi qator oxiriga qo'shiladi. writeFile bilan har so'rov oldingilarni o'chirib, faqat oxirgisi qolardi.

5. Xatolar: ENOENT va boshqalar

5.1 Xato obyektida nima bor

Yo'q faylni o'qiymiz:

js
import { readFile } from "node:fs/promises";

try {
  await readFile("yoq.json", "utf8");
} catch (xato) {
  console.log(xato.code);
  console.log(xato.syscall);
  console.log(xato instanceof Error);
}

Konsolda:

text
ENOENT
open
true

fs xatolarida oddiy message dan tashqari maxsus maydonlar bor:

Maydon Qiymat Ma'nosi
code "ENOENT" Xato turi — dasturda shunga qarang
syscall "open" Qaysi tizim chaqiruvi yiqildi
path to'liq yo'l Qaysi fayl
errno -4058 (Windows) OS raqami — platformaga bog'liq

Biz Windows'da ishga tushirdik. message va errno shunday chiqdi:

js
console.log(xato.errno);
console.log(xato.message);
text
-4058
ENOENT: no such file or directory, open 'C:\Users\…\yoq.json'

Tarjimasi: "bunday fayl yoki papka yo'q, ochish (open) paytida". Biz nisbiy "yoq.json" bergan edik, xabarda esa to'liq yo'l. errno Linux'da boshqa raqam (hujjatga ko'ra -2; biz Linux'da sinamadik). Shuning uchun qoida: code ni tekshiring — u hamma OS'da bir xil. message va errno ga tayanmang.

ENOENT — "Error NO ENTry", ya'ni "bunday yozuv yo'q". Nomi g'alati, lekin 1970-yillardagi Unix'dan qolgan va hamma joyda shunday.

5.2 Eng ko'p uchraydigan kodlar

code Qachon Odatiy javob
ENOENT Fayl yoki papka yo'q Sukut qiymat yoki "yarating" deb xabar
EACCES / EPERM Ruxsat yo'q Foydalanuvchiga aniq xabar
EISDIR Faylning o'rniga papka berildi Yo'lni tekshirish
EEXIST Fayl allaqachon bor ("wx" bilan) Boshqa nom
ENOTDIR Yo'l o'rtasidagi qism papka emas Yo'lni tekshirish
EMFILE Ochiq fayllar juda ko'p Xavfsiz fayl operatsiyalari darsida

EISDIR ni ko'ramiz — papkani fayl deb o'qish:

js
import { readFile } from "node:fs/promises";

try {
  await readFile(".", "utf8");
} catch (xato) {
  console.log(xato.code, "—", xato.message);
}

Konsolda:

text
EISDIR — EISDIR: illegal operation on a directory, read

"Papka ustida noto'g'ri amal, o'qish paytida". Bu safar message da yo'l yo'q — xato open da emas, read da chiqdi.

5.3 Ushlanmagan xato

try/catch siz await readFile yo'q faylda butun dasturni to'xtatadi. Biz ishga tushirdik (izning bir qismi):

js
import { readFile } from "node:fs/promises";

const matn = await readFile("yoq.json", "utf8");
console.log(matn);
text
Error: ENOENT: no such file or directory, open 'C:\Users\…\yoq.json'
    at async open (node:internal/fs/promises:1345:25)
    at async readFile (node:internal/fs/promises:1996:14) {
  errno: -4058,
  code: 'ENOENT',
  syscall: 'open',
  path: 'C:\\Users\\…\\yoq.json'
}

Node xato obyektining maydonlarini ham { … } ichida chiqardi. console.log(matn) ga navbat kelmadi, jarayon exit kod 1 bilan tugadi (process darsi).

5.4 Naqsh: "yo'q bo'lsa — sukut, boshqa xato — yuqoriga"

Hamma xatoni bir xil ushlash xavfli. Sozlama fayli yo'q — bu normal holat, sukut qiymat bilan davom etamiz. Lekin ruxsat yo'q (EACCES) — bu muammo, uni yashirsak, foydalanuvchi nega sozlamasi ishlamayotganini hech qachon bilmaydi.

js
import { readFile, writeFile } from "node:fs/promises";

const SUKUT = { valyutalar: ["USD", "EUR", "RUB"] };

async function sozlamaniOqi(fayl) {
  let matn;
  try {
    matn = await readFile(fayl, "utf8");
  } catch (xato) {
    if (xato.code === "ENOENT") return SUKUT; // yo'q — normal
    throw xato; // boshqa xato — yashirmaymiz
  }
  return JSON.parse(matn);
}

await writeFile("bor.json", '{"valyutalar": ["KZT"]}');
console.log(await sozlamaniOqi("yoq.json"));
console.log(await sozlamaniOqi("bor.json"));

Konsolda:

text
{ valyutalar: [ 'USD', 'EUR', 'RUB' ] }
{ valyutalar: [ 'KZT' ] }

catch ichidagi throw xato — qayta tashlash. Biz faqat tanigan xatoni "yutamiz", qolganini yuqoriga uzatamiz.

Diqqat: "Avval tekshir, keyin o'qi" (existsSync keyin readFile) — ko'p uchraydigan, lekin noto'g'ri naqsh. Tekshiruv va o'qish orasida fayl o'chirilishi mumkin — xato baribir chiqadi va kod ikki marta diskka murojaat qiladi. To'g'ri yo'l — darhol o'qish va ENOENT ni ushlash. Bu xatoning nomi — TOCTOU (time-of-check to time-of-use: tekshirish va ishlatish vaqti farqi). Uni xavfsiz fayl operatsiyalari darsida chuqurroq ko'ramiz.

Tekshirib ko'ring: catch (xato) { return SUKUT; } — hamma xatoda sukut qaytaradi. Qaysi holatda bu foydalanuvchini aldaydi?

Javob

Fayl bor, lekin uni o'qishga ruxsat yo'q (EACCES) yoki yo'l papkaga ishora qiladi (EISDIR). Foydalanuvchi sozlama yozgan, lekin dastur indamay sukutni ishlatadi — "sozlamam ishlamayapti" degan sirli muammo paydo bo'ladi. Xato ko'rinsa, sababini darhol topadi.

6. Bir nechta faylni birdan

Promise shaklining katta afzalligi — bir nechta amalni parallel boshlash. Promise kombinatorlari shu yerda ham ishlaydi:

js
import { readFile, stat, writeFile } from "node:fs/promises";

await writeFile("kurslar.json", '[{"Ccy": "USD"}]');
const [s, matn] = await Promise.all([
  stat("kurslar.json"),
  readFile("kurslar.json", "utf8"),
]);
console.log(s.size, JSON.parse(matn)[0].Ccy);

Konsolda:

text
16 USD

stat — fayl haqida ma'lumot (hajm, o'zgargan vaqt) beradi; u fs chuqur darsida. Ikkala amal bir vaqtda boshlandi va ikkalasi tugaganda await qaytdi. valyuta kesh faylini aynan shunday o'qiydi.

Uzoq o'qishni to'xtatish ham mumkin: readFile signal opsiyasini qabul qiladi (AbortSignal.timeout(1000) — AbortController). Kichik fayllar uchun bu kam kerak bo'ladi.

7. Sozlama faylini tekshirish (jonli)

JSON.parse faqat sintaksisni tekshiradi. {"valyutalar": "USD"} to'g'ri JSON, lekin valyutalar massiv emas. Fayldan kelgan narsaga ishonmaymiz — tarmoqdan kelgan javobga ishonmaganimiz kabi. Tekshiruvchi funksiya — toza JS, unda fs yo'q. Shuning uchun bu blok saytda ham «Ishga tushir» bilan ishlaydi:

js
const SUKUT = { valyutalar: ["USD", "EUR", "RUB"] };

class SozlamaXatosi extends Error {}

function sozlamaniTekshir(x, fayl) {
  if (typeof x !== "object" || x === null || Array.isArray(x)) {
    throw new SozlamaXatosi(`${fayl}: obyekt kutilgan`);
  }
  const { valyutalar = SUKUT.valyutalar } = x;
  const togri = Array.isArray(valyutalar) && valyutalar.every(
    (v) => typeof v === "string" && /^[A-Za-z]{3}$/.test(v),
  );
  if (!togri) {
    throw new SozlamaXatosi(
      `${fayl}: "valyutalar" 3 harfli kodlar massivi bo'lishi kerak`,
    );
  }
  return { valyutalar: valyutalar.map((v) => v.toUpperCase()) };
}

const misollar = [
  {},
  { valyutalar: ["usd", "kzt"] },
  { valyutalar: "USD" },
  [],
];
for (const x of misollar) {
  try {
    console.log(sozlamaniTekshir(x, "config.json"));
  } catch (xato) {
    console.log(xato.message);
  }
}

Konsolda:

text
{ valyutalar: [ 'USD', 'EUR', 'RUB' ] }
{ valyutalar: [ 'USD', 'KZT' ] }
config.json: "valyutalar" 3 harfli kodlar massivi bo'lishi kerak
config.json: obyekt kutilgan

Bo'sh obyekt {} — sukut valyutalar (destrukturlashdagi sukut qiymat). Kichik harflar katta harfga aylandi. Qolgan ikkitasi — aniq xabar bilan xato. Saytdagi konsol obyektni biroz boshqacha ko'rsatishi mumkin ({valyutalar: Array(3)}) — ma'nosi bir xil.

8. CLI qadami: sozlama.ts (holat 18, 1-qism)

Holat 18 to'rt darsga bo'lingan: bu dars — fayl o'qish, fs chuqur — stat va glob, xavfsiz fayl operatsiyalari — atomik yozish, path va os — papkalar joyi. Hali commit qilmang — commit to'rtinchi darsda.

Bugun src/sozlama.ts ni yozasiz. Kanondagi o'qish funksiyasi:

ts
export async function sozlamaniOqi(papka: string): Promise<Sozlama> {
  const fayl = path.join(papka, "config.json");
  let matn: string;
  try {
    matn = await readFile(fayl, "utf8");
  } catch (xato) {
    if (xato instanceof Error && "code" in xato && xato.code === "ENOENT") return SUKUT;
    throw xato;
  }
  try {
    return sozlamaniTekshir(JSON.parse(matn), fayl);
  } catch (xato) {
    if (xato instanceof SyntaxError) throw new SozlamaXatosi(`${fayl}: JSON xato — ${xato.message}`);
    throw xato;
  }
}

Biz JS'da yozgan naqshdan ikki farq bor:

  1. TypeScript'da catch (xato) — unknown. xato.code ni to'g'ridan o'qib bo'lmaydi, avval instanceof Error && "code" in xato bilan toraytiriladi (tip toraytirish).
  2. Ikkita alohida try. Birinchisi — faylni o'qish xatolari, ikkinchisi — JSON xatosi. Buzilgan JSON (SyntaxError) foydalanuvchiga tushunarli SozlamaXatosi ga aylanadi: qaysi fayl, nima xato. cli.ts uni ushlaydi va exit kod 2 bilan chiqadi — xato jim yutilmaydi.

Kanon testlari sozlamaning to'rt holatini tekshiradi: fayl yo'q → sukut, to'g'ri fayl, buzilgan JSON va noto'g'ri tur → xato. Fayl qayerda yotishi (papka) — path va os darsida.

cli.ts da valyuta kurs kodsiz chaqirilsa, sozlamadagi valyutalar olinadi. Holat 18 tugagach kanon natijasi (fixture bilan, config.json yo'q — sukut USD EUR RUB):

text
$ valyuta kurs
Ogohlantirish: offline rejim — saqlangan namuna kurslar (fixtures/) ishlatildi
2026-10-23 holatiga (fixture)
USD    1    11 903.15   -62.36  AQSH dollari
EUR    1    13 329.15  -108.12  EVRO
RUB    1       146.75     0.79  Rossiya rubli

9. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
<Buffer 7b 22 …> chiqdi Encoding berilmagan readFile(f, "utf8")
[object Promise] yoki Promise { <pending> } await unutilgan await readFile(…)
Cannot read properties of undefined callback'da (matn, xato) — tartib teskari Callback'da birinchi — xato
Fayl topilmaydi, lekin u bor Nisbiy yo'l cwd'ga nisbatan path va os darsidagi import.meta.dirname
Oldingi ma'lumot yo'qoldi writeFile almashtiradi Qo'shish — appendFile
Hamma xato "sozlama yo'q" deb chiqadi catch hamma xatoni yutadi code === "ENOENT" ni tekshirib, qolganini throw
require is not defined ESM faylda CJS sintaksis import … from "node:fs"

Nisbiy yo'l tuzog'i alohida e'tiborga loyiq. readFile("config.json") faylni joriy papka (cwd — terminal turgan papka)dan qidiradi, skript yotgan papkadan emas. valyuta ni boshqa papkadan chaqirsangiz, u boshqa config.json ni qidiradi. Shuning uchun valyuta hech qachon nisbiy yo'l ishlatmaydi — har doim to'liq yo'l quradi.

10. Mashqlar

1-mashq (oson): qaysi shakl?

  • Natijani shu qatorning o'zida qaytaradigan shakl: readFile
  • Yangi kod uchun tavsiya qilingan modul: node:fs/
  • Encodingsiz readFile qaytaradigan tur:
  • Fayl yo'qligini bildiruvchi code:
  • Faylni almashtirmay, oxiriga yozadigan funksiya:
Yechim
  • Sync — readFileSync: JS thread kutadi.
  • promises — await bilan ishlaydi.
  • Buffer — xom baytlar; matn uchun "utf8".
  • ENOENT — "Error NO ENTry".
  • appendFile — log va tarix uchun.

2-mashq (o'rta): so'rovlar hisoblagichi

hisobla(fayl) funksiyasini yozing: fayldan sonni o'qiydi, 1 ga oshiradi, qaytadan yozadi va yangi sonni qaytaradi. Fayl yo'q bo'lsa — 0 dan boshlaydi. Boshqa xatolar — yuqoriga. Uch marta chaqiring. Natija 1, 2, 3 bo'lsin (dastur boshida faylni o'chiring: rm(fayl, { force: true }) — force fayl yo'q bo'lsa xato bermaydi).

Yechim
js
import { readFile, rm, writeFile } from "node:fs/promises";

async function hisobla(fayl) {
  let son = 0;
  try {
    son = Number(await readFile(fayl, "utf8"));
  } catch (xato) {
    if (xato.code !== "ENOENT") throw xato;
  }
  son += 1;
  await writeFile(fayl, String(son));
  return son;
}

await rm("hisob.txt", { force: true });
console.log(await hisobla("hisob.txt"));
console.log(await hisobla("hisob.txt"));
console.log(await hisobla("hisob.txt"));

Konsolda:

text
1
2
3

rm bo'lmasa, dasturni ikkinchi marta ishga tushirganda 4 5 6 chiqadi — fayl diskda qoladi. Diskdagi holat dastur tugagach ham yashaydi, bu o'zgaruvchidan asosiy farqi. Yana bir tuzoq: ikki jarayon bir vaqtda hisobla qilsa, ikkalasi ham 3 ni o'qib, 4 yozishi mumkin — bitta hisob yo'qoladi. Bunday "poyga"lar haqida xavfsiz fayl operatsiyalari darsida.

3-mashq (qiyin): CSV'dan kurslar

kurslar.csv faylini yarating (writeFile bilan) va uni o'qib, har valyuta uchun "USD: 11 903.15" ko'rinishidagi qatorni chiqaring. Fayl:

text
kod,kurs
USD,11903.15
EUR,13329.15
RUB,146.75

Talablar: promise shakli; sarlavha qatorini tashlab yuboring; bo'sh qatorlarni e'tiborsiz qoldiring; sonni ming bo'yicha bo'sh joy bilan yozing (toLocaleString emas — satr metodlari va regex bilan). Fayl yo'q bo'lsa — "kurslar.csv topilmadi" deb chiqaring.

Yechim
js
import { readFile, writeFile } from "node:fs/promises";

const sonniYoz = (n) => {
  const [butun, kasr] = n.toFixed(2).split(".");
  return `${butun.replace(/\B(?=(\d{3})+(?!\d))/g, " ")}.${kasr}`;
};

async function kurslarniChiqar(fayl) {
  let matn;
  try {
    matn = await readFile(fayl, "utf8");
  } catch (xato) {
    if (xato.code === "ENOENT") {
      console.log(`${fayl} topilmadi`);
      return;
    }
    throw xato;
  }
  const qatorlar = matn.split("\n").slice(1).filter((q) => q.trim());
  for (const q of qatorlar) {
    const [kod, kurs] = q.split(",");
    console.log(`${kod}: ${sonniYoz(Number(kurs))}`);
  }
}

const csv = "kod,kurs\nUSD,11903.15\nEUR,13329.15\nRUB,146.75\n\n";
await writeFile("kurslar.csv", csv);
await kurslarniChiqar("kurslar.csv");
await kurslarniChiqar("boshqa.csv");

Konsolda:

text
USD: 11 903.15
EUR: 13 329.15
RUB: 146.75
boshqa.csv topilmadi

Fayl oxiridagi ikkita \n bo'sh qatorlar beradi — filter ularni olib tashladi. Windows'da yaratilgan CSV'da qatorlar \r\n bilan tugashi mumkin, unda kurs oxirida \r qoladi. Number("146.75\r") baribir son beradi, lekin matn sifatida ishlatsangiz muammo bo'ladi. Bu CRLF tuzog'i — path va os darsida.

4-mashq: CLI qadami

src/sozlama.ts ni yozing: SUKUT, SozlamaXatosi, sozlamaniTekshir va sozlamaniOqi («CLI qadami» bo'limidagi kod). Hozircha papka sifatida vaqtinchalik process.cwd() bering. Uch holatni qo'lda sinang: fayl yo'q, to'g'ri config.json, buzilgan JSON ({"valyutalar": [). Commit qilmang.

Yechim

Buzilgan JSON uchun kutilgan xabar boshlanishi: config.json: JSON xato — … va JSON.parse ning o'z xabari (masalan Unexpected end of JSON input). npm run tip va npm run lint toza bo'lsin. papka ni to'g'ri joyga — OS sozlama papkasiga — path va os darsida ulaymiz.

11. Real ishda

  • Konfiguratsiya fayllari. ESLint, Prettier, TypeScript, Vite — hammasi ishga tushganda fs bilan sozlama faylini qidiradi va o'qiydi. "Yo'q bo'lsa — sukut" naqshi aynan shu asboblarda.
  • Server. Express yoki NestJS serverida fayl o'qish — faqat promise shakli. Sync shakl faqat ishga tushishda (sertifikat, sozlama) ishlatiladi. Serverni keyingi qismda yozamiz.
  • Intervyu savoli: "readFileSync va readFile farqi nima va qachon qaysi biri?" — javob event loop orqali.
  • Xato kodlari. Log yoki Sentry'da ENOENT, EACCES, EMFILE ni ko'rganda ma'nosini darhol bilish — tajribali backend dasturchining belgisi.

Xulosa

  • fs dagi har amal uch shaklda: sync (kutadi), callback (eski), promise (node:fs/promises — yangi kod uchun).
  • Encoding bermasangiz — Buffer; matn uchun "utf8". Noto'g'ri encoding — "krakozyabra".
  • writeFile almashtiradi, appendFile qo'shadi; flag, mode — uchinchi parametrda.
  • Xatoda code ga qarang (ENOENT, EACCES, EISDIR): u har OS'da bir xil, message va errno esa yo'q.
  • "Yo'q bo'lsa — sukut, boshqa xato — throw" va "tekshirmay o'qi, xatoni ushla" — fayl kodining asosiy naqshlari.

Keyingi dars: fs chuqur: kataloglar, stat, glob va watch — papka yaratish, aylanib chiqish, fayl hajmi va yoshi, valyuta kesh buyrug'i.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Node.js fs moduli: uch API shakli — sync, callback va promise — IlmHamroh