IlmHamroh
JavaScript Full-stack/12-qism. JavaScript: ilg'or mavzular va kod sifati27/44-dars22 daqiqa
Mundarija (33)

JavaScript'da Result va Maybe: xato va bo'sh qiymatlar bilan FP uslubida ishlash

Qisqacha: Xato kutilgan holat bo'lsa (noto'g'ri forma, buzuq JSON, 404), uni throw qilish o'rniga qiymat sifatida qaytarish mumkin: { ok: true, qiymat } yoki { ok: false, xato }. Bu — Result naqshi. Bo'sh qiymat uchun xuddi shu g'oya — Maybe: "qiymat bor yoki yo'q". JavaScript'da Maybe vazifasini ko'pincha ?. va ?? bajaradi. Result esa kichik keyin yordamchisi bilan pipe ga ulanadi: birinchi xatoda zanjir o'zi to'xtaydi.

Bu darsda

  • null va throw ning qaysi joylarda noqulay ekanini va Result, Maybe bu muammoni qanday hal qilishini tushuntira olasiz.
  • muvaffaq, xatoli, ozgartir, keyin yordamchilarini yozib, Result qaytaradigan funksiyalarni pipe bilan ulaysiz.
  • Ikki xil validatsiyani ajratasiz: birinchi xatoda to'xtash va hamma xatoni yig'ish.
  • throw qiladigan kod va fetch ni chegarada Result'ga aylantirasiz.
  • Qachon throw, qachon Result ishlatishni asoslab bera olasiz.

Oldin bilishingiz kerak: Xato strategiyasi, Memoization, Kompozitsiya: compose va pipe, Currying va partial application, ?? va ?..

1. Nega bu kerak?

Memoization darsida toza funksiyaning foydasini yana bir bor ko'rdik: bir xil kirish — bir xil chiqish. Lekin hamma funksiya ham doim "yaxshi" javob bermaydi. Mehmon formani noto'g'ri to'ldiradi, server 404 qaytaradi, localStorage dagi JSON buziladi. Bunday holatlarda JavaScript'da ikki odatiy yo'l bor, va ikkalasining ham kamchiligi bor.

Birinchi yo'l — null qaytarish. find topolmasa undefined, match topolmasa null beradi. Chaqiruvchi tekshirishni unutsa:

js
const taomlar = [{ nom: "Osh", narx: 35000 }];
const taom = taomlar.find((t) => t.nom === "Somsa");

console.log(taom.narx);
text
TypeError: Cannot read properties of undefined (reading 'narx')

Xato find qatorida emas, keyinroq — taom.narx da chiqdi. Katta dasturda bu "keyinroq" boshqa faylda, bir necha soatdan keyin bo'lishi mumkin.

Ikkinchi yo'l — throw. Xato strategiyasi darsida ko'rdik: throw ni e'tiborsiz qoldirib bo'lmaydi. Lekin funksiyaning nomidan uning xato tashlashi ko'rinmaydi. paketniOqi(matn) ni chaqirgan dasturchi try...catch kerakligini faqat ichini o'qib bilib oladi. Bundan tashqari, throw birinchi xatoda to'xtaydi — forma tekshiruvida bu noqulay.

O'sha darsda uchinchi yo'lni ham ko'rgan edik — Result naqshi: funksiya doim obyekt qaytaradi, { ok: true, qiymat } yoki { ok: false, xatolar }. "Result'ning to'liq shakli — keyinroq" degan edik. Bugun shu va'dani bajaramiz: Result'ni kichik yordamchilar bilan qulay qilamiz, uni pipe ga ulaymiz va uning egizagi — Maybe bilan tanishamiz.

Hayotdan o'xshatish: pochta posilkasi. Ichida narsa bo'lishi ham, bo'lmasligi ham mumkin. Lekin quti doim keladi. Qutini ochmasdan oldin uning ustidagi yorliqqa qaraysiz: "yetkazildi" yoki "qaytarildi, sababi: manzil noto'g'ri". null — qutining o'zi kelmagani. throw — pochtachi eshik oldida baqirgani. Result — yorliqli quti.

2. Bo'sh qiymat: Maybe g'oyasi

2.1 JavaScript'ning o'z vositasi: ?. va ??

Ichma-ich ma'lumotda har bosqich bo'sh bo'lishi mumkin. Mehmonning manzili bo'lmasligi, manzilda shahar bo'lmasligi mumkin. ?? va ?. darsidan bunga tayyor javob bilamiz:

js
const mehmonlar = [
  { ism: "Dilshod aka", manzil: { shahar: "Samarqand" } },
  { ism: "Malika", manzil: {} },
  { ism: "Jasur" },
];

for (const mehmon of mehmonlar) {
  const shahar = mehmon.manzil?.shahar ?? "Toshkent";
  console.log(`${mehmon.ism}: ${shahar}`);
}

Konsolda:

text
Dilshod aka: Samarqand
Malika: Toshkent
Jasur: Toshkent

?. zanjirni bo'sh qiymatda to'xtatadi va undefined qaytaradi. ?? esa bo'sh qiymat o'rniga zaxira qiymatni beradi. Ikkalasi birga — "qiymat bo'lsa, ishla; bo'lmasa, jim o't va oxirida zaxira ber".

2.2 Maybe — shu g'oyaning nomi

Funksional dasturlashda bu g'oya Maybe (boshqa tillarda Option) deb ataladi: "ehtimol qiymat bor, ehtimol yo'q" degan quti. Uning ustida amallar bajarasiz. Quti bo'sh bo'lsa, amallar jimgina o'tkazib yuboriladi. Oxirida zaxira qiymat bilan qutini ochasiz.

G'oyani ko'rish uchun eng kichik Maybe'ni yozamiz:

js
const ehtimol = (qiymat) => ({
  ozgartir: (fn) =>
    qiymat == null ? ehtimol(null) : ehtimol(fn(qiymat)),
  yoki: (zaxira) => qiymat ?? zaxira,
});

const narxlar = { osh: 35000, manti: 30000 };

const narxMatni = (nom) =>
  ehtimol(narxlar[nom])
    .ozgartir((narx) => narx * 2)
    .ozgartir((narx) => `${narx} so'm`)
    .yoki("menyuda yo'q");

console.log(narxMatni("osh")); // 70000 so'm
console.log(narxMatni("somsa")); // menyuda yo'q

qiymat == null — ikkita tenglik belgisi bilan bu tekshiruv null ni ham, undefined ni ham ushlaydi. Kursda odatda === yozamiz; bu — == null ning yagona keng tarqalgan qo'llanilishi. "somsa" uchun narxlar[nom] — undefined. Ikkala ozgartir ham ishlamadi, yoki zaxirani berdi. Xato chiqmadi.

?. bilan solishtiring: ehtimol(...).ozgartir(...) — bu ?. ning funksiyalar uchun umumlashgan shakli. JavaScript'da obyekt xususiyatlari uchun ?. va ?? deyarli doim yetarli. Shuning uchun kundalik kodda o'z Maybe'ingizni yozmaysiz. Lekin bu g'oyani bilish kerak: Rust, Swift, Kotlin tillarida va TypeScript kutubxonalarida u har qadamda uchraydi.

2.3 ?? va || farqi — yana bir bor

Bo'sh qiymat bilan ishlashdagi eng ko'p xato — || ishlatish. || faqat null/undefined ni emas, har qanday falsy qiymatni almashtiradi: 0, "", false.

js
const choy = { nom: "Ko'k choy", narx: 0, izoh: "" };

console.log(choy.narx || "narx yo'q"); // narx yo'q
console.log(choy.narx ?? "narx yo'q"); // 0
console.log(choy.izoh ?? "izohsiz"); // (bo'sh satr)

Aksiya kuni choy bepul — narx 0. || uni "narx yo'q" deb ko'rsatdi — bu xato. ?? esa 0 ni haqiqiy qiymat deb qoldirdi. Uchinchi qatorda konsolga bo'sh satr chiqadi. Qoida: "bo'sh" deganda null va undefined ni nazarda tutsangiz — ??.

Tekshirib ko'ring: ehtimol(0).ozgartir((x) => x + 5).yoki(100) nima qaytaradi?

Javob

5. 0 == null — false, ya'ni 0 bo'sh qiymat hisoblanmaydi. ozgartir ishladi: 0 + 5 = 5. yoki esa ?? bilan yozilgan, u ham 5 ni o'zgartirmaydi. Agar ehtimol ichida qiymat ? ... : ... deb yozsak, 0 noto'g'ri "bo'sh" deb qolardi.

3. Result: xato — qiymat

3.1 Ikki yasovchi

Result uchun ikkita kichik funksiya yozamiz. Ular faqat obyekt yasaydi, lekin kodni bir xil shaklda ushlab turadi:

js
const muvaffaq = (qiymat) => ({ ok: true, qiymat });
const xatoli = (xato) => ({ ok: false, xato });

const odamSoniniOl = (matn) => {
  const son = Number(matn);
  if (!Number.isInteger(son)) {
    return xatoli("Odam soni butun son bo'lsin.");
  }
  return muvaffaq(son);
};

console.log(odamSoniniOl("4")); // { ok: true, qiymat: 4 }
console.log(odamSoniniOl("to'rt"));

Konsolda:

text
{ ok: true, qiymat: 4 }
{ ok: false, xato: "Odam soni butun son bo'lsin." }

Shakl 10-qismdagidek — ok maydoni. Farqi bitta: xato xato maydonida, bittalik. Hamma xatolarni yig'ish kerak bo'lganda massiv bo'ladi — «Funksional validatsiya» bo'limida.

Endi funksiyaning natijasi o'zi ogohlantiradi: "men muvaffaqiyatsiz bo'lishim mumkin". Chaqiruvchi qiymat ga yetish uchun ok ga qarashga majbur. throw dan farqli o'laroq, bu yashirin emas.

3.2 Chegarada: throw ni Result'ga aylantirish

Ko'p tayyor funksiyalar xato tashlaydi: JSON.parse, new URL, structuredClone. Ularni o'zgartira olmaymiz. Lekin ularni Result qaytaradigan funksiyaga o'rash mumkin:

js
const muvaffaq = (qiymat) => ({ ok: true, qiymat });
const xatoli = (xato) => ({ ok: false, xato });

const xavfsiz = (funksiya) => (...argumentlar) => {
  try {
    return muvaffaq(funksiya(...argumentlar));
  } catch (xato) {
    return xatoli(xato.message);
  }
};

const jsonniOqi = xavfsiz(JSON.parse);

console.log(jsonniOqi('{"odam": 4}'));
// { ok: true, qiymat: { odam: 4 } }
console.log(jsonniOqi("{odam: 4}").ok); // false

xavfsiz — yuqori tartibli funksiya: istalgan "xato tashlovchi" funksiyani "Result qaytaruvchi"ga aylantiradi. try...catch endi bir joyda — chegarada. Qolgan kod faqat ok bilan ishlaydi. Bu Xato strategiyasi darsidagi "chegarada ushla" qoidasining funksional shakli.

Tekshirib ko'ring: xavfsiz(Number)("abc") nima qaytaradi?

Javob

{ ok: true, qiymat: NaN }. Number("abc") xato tashlamaydi — u NaN qaytaradi. xavfsiz faqat tashlangan xatoni ushlaydi. NaN ni xato deb hisoblash uchun alohida tekshiruv kerak — odamSoniniOl dagidek.

4. Result zanjiri

4.1 Har qadamda if (natija.ok) — charchatadi

Bron so'rovi uch bosqichdan o'tadi: JSON'ni o'qish, odam sonini tekshirish, vaqtni tekshirish. Har bosqich Result qaytaradi. Oddiy if bilan:

js
const birinchi = jsonniOqi(matn);
if (!birinchi.ok) return birinchi;
const ikkinchi = odamniTekshir(birinchi.qiymat);
if (!ikkinchi.ok) return ikkinchi;
const uchinchi = vaqtniTekshir(ikkinchi.qiymat);
if (!uchinchi.ok) return uchinchi;
return uchinchi;

Ishlaydi, lekin har bosqichda bir xil ikki qator takrorlanadi. Bu takrorni funksiyaga chiqaramiz.

4.2 keyin va ozgartir

Ikki yordamchi yetarli. Ikkalasi ham curried — avval funksiya, oxirida Result. Shunda ular pipe ga to'g'ridan-to'g'ri sig'adi:

js
const muvaffaq = (qiymat) => ({ ok: true, qiymat });
const xatoli = (xato) => ({ ok: false, xato });

// Result qaytaradigan funksiyani ulaydi
const keyin = (fn) => (natija) =>
  natija.ok ? fn(natija.qiymat) : natija;
// Oddiy qiymat qaytaradigan funksiyani ulaydi
const ozgartir = (fn) => (natija) =>
  natija.ok ? muvaffaq(fn(natija.qiymat)) : natija;

const ikkiBaravar = ozgartir((x) => x * 2);
const musbatmi = keyin((x) =>
  x > 0 ? muvaffaq(x) : xatoli("Manfiy"),
);

console.log(ikkiBaravar(muvaffaq(5))); // { ok: true, qiymat: 10 }
console.log(musbatmi(muvaffaq(-3))); // { ok: false, xato: 'Manfiy' }
console.log(ikkiBaravar(xatoli("Yo'q")));
// { ok: false, xato: "Yo'q" }

ozgartir nomi Maybe'dagi ozgartir bilan bir xil — bu ataylab: ma'nosi ham bir, "quti ichidagi qiymatga funksiyani qo'lla".

Ikkalasining qoidasi bir: Result muvaffaqiyatli bo'lsa — funksiyani qo'lla; xatoli bo'lsa — hech narsa qilmasdan uzat. Farqi — funksiya nima qaytarishida:

Yordamchi Funksiya qaytaradi Misol
ozgartir(fn) oddiy qiymat x * 2, trim()
keyin(fn) Result tekshiruv, jsonniOqi

Jadvaldan to'ldiring: (x) => x.trim() kabi oddiy qiymat qaytaradigan funksiya Result zanjiriga bilan ulanadi.

Kutubxonalarda ularning nomlari: map (ozgartir) va flatMap, chain yoki andThen (keyin). Massivdagi map va flatMap bilan o'xshashlik tasodif emas: flatMap ham "massiv qaytaradigan" funksiyani ichma-ich massivsiz ulaydi.

4.3 Temir yo'l: ikki rels

Result zanjirini ko'pincha temir yo'l bilan tasvirlashadi. Ingliz tilida bu yondashuv "railway oriented programming" deb ataladi. Ikki rels bor: yuqorida — muvaffaqiyat, pastda — xato. Har bosqich — yo'l almashtirgich: muvaffaqiyat relsidagi poyezdni tekshiradi va kerak bo'lsa xato relsiga o'tkazadi. Xato relsiga tushgan poyezd qolgan bosqichlarni aylanib o'tib, oxirigacha boradi:

flowchart LR
  K["kirish matni"] --> A["jsonniOqi"]
  A -- "ok" --> B["odamniTekshir"]
  B -- "ok" --> C["vaqtniTekshir"]
  C -- "ok" --> OK["muvaffaq(bron)"]
  A -- "xato" --> X["xatoli(sabab)"]
  B -- "xato" --> X
  C -- "xato" --> X

Diagrammada xato yo'llari bitta joyga yig'ilganiga qarang. Qaysi bosqichda xato chiqmasin, natija bir xil shaklda — { ok: false, xato } — oxiriga yetadi.

4.4 «Bahor» bron tekshiruvi pipe bilan

Endi hammasini birlashtiramiz. Bosqichlar — oddiy funksiyalar, pipe — Kompozitsiya darsidan:

js
const pipe = (...bosqichlar) => (kirish) =>
  bosqichlar.reduce((natija, bosqich) => bosqich(natija), kirish);
const muvaffaq = (qiymat) => ({ ok: true, qiymat });
const xatoli = (xato) => ({ ok: false, xato });
const keyin = (fn) => (natija) =>
  natija.ok ? fn(natija.qiymat) : natija;

const jsonniOqi = (matn) => {
  try {
    return muvaffaq(JSON.parse(matn));
  } catch {
    return xatoli("So'rov JSON emas.");
  }
};
const odamniTekshir = (bron) =>
  bron.odam >= 1 && bron.odam <= 20
    ? muvaffaq(bron)
    : xatoli("Odam soni 1–20 bo'lsin.");
const vaqtniTekshir = (bron) =>
  bron.vaqt >= "10:00" && bron.vaqt <= "22:00"
    ? muvaffaq(bron)
    : xatoli("Bron vaqti 10:00–22:00.");

const bronniTekshir = pipe(
  jsonniOqi,
  keyin(odamniTekshir),
  keyin(vaqtniTekshir),
);

console.log(bronniTekshir('{"odam": 4, "vaqt": "19:00"}'));
console.log(bronniTekshir('{"odam": 40, "vaqt": "23:30"}'));
console.log(bronniTekshir("odam=4"));

Konsolda:

text
{ ok: true, qiymat: { odam: 4, vaqt: '19:00' } }
{ ok: false, xato: "Odam soni 1–20 bo'lsin." }
{ ok: false, xato: "So'rov JSON emas." }

catch dan keyin qavs yo'q — catch {. Xato obyektining o'zi kerak bo'lmasa, uni yozmaslik mumkin.

bronniTekshir ni yuqoridan pastga o'qing: "JSON'ni o'qi, keyin odamni tekshir, keyin vaqtni tekshir". Bitta ham if (natija.ok) yo'q — bu ishni keyin qildi. Ikkinchi so'rovda ikkita xato bor edi (40 kishi va 23:30), lekin faqat birinchisi chiqdi. Zanjir birinchi xatoda xato relsiga o'tdi va vaqtniTekshir umuman chaqirilmadi. Bu — fail fast (Xato strategiyasi).

Qadamlarda ikkinchi so'rov relsdan qanday tushishini kuzating:

Tekshirib ko'ring: bronniTekshir da keyin(odamniTekshir) o'rniga ozgartir(odamniTekshir) yozilsa, '{"odam": 40, "vaqt": "19:00"}' uchun nima chiqadi?

Javob

Result ichida Result: { ok: true, qiymat: { ok: false, xato: ... } }. ozgartir funksiya oddiy qiymat qaytaradi deb hisoblaydi va uni yana muvaffaq ga o'raydi. Tashqi quti "muvaffaqiyat" deydi, ichida esa xato yashiringan. Keyingi keyin(vaqtniTekshir) esa bron.vaqt o'rniga Result obyektini oladi. Qoida: Result qaytaradigan funksiya — keyin bilan.

5. Funksional validatsiya: hamma xatoni yig'ish

Zanjir birinchi xatoda to'xtaydi. JSON o'qilmasa, odam sonini tekshirishning ma'nosi yo'q — bu to'g'ri. Lekin forma maydonlari bir-biriga bog'liq emas. Mehmon uchala xatoni birdaniga ko'rishi kerak — 10-qismdagi bronniTekshir shuning uchun xatolar massivini yig'gan edi.

Endi uni funksional uslubda qayta yozamiz. Har qoida — kichik funksiya: bronni oladi, xato bo'lsa xabarli massiv, xato bo'lmasa bo'sh massiv qaytaradi. Qoidalarni yasash uchun curried fabrika:

js
const muvaffaq = (qiymat) => ({ ok: true, qiymat });

const qoida = (maydon, togrimi, xabar) => (bron) =>
  togrimi(bron[maydon]) ? [] : [{ maydon, xabar }];

const bronQoidalari = [
  qoida("ism", (ism) => ism.trim().length >= 2, "Ismingizni yozing."),
  qoida("odam", (son) => son >= 1 && son <= 20, "1–20 kishi."),
  qoida("vaqt", (v) => v >= "10:00" && v <= "22:00", "10:00–22:00."),
];

const tekshir = (qoidalar) => (bron) => {
  const xatolar = qoidalar.flatMap((q) => q(bron));
  return xatolar.length === 0
    ? muvaffaq(bron)
    : { ok: false, xatolar };
};

const bronniTekshir = tekshir(bronQoidalari);
const natija = bronniTekshir({ ism: " ", odam: 25, vaqt: "19:00" });

if (natija.ok) {
  console.log("Bron qabul qilindi");
} else {
  for (const x of natija.xatolar) {
    console.log(`${x.maydon}: ${x.xabar}`);
  }
}

Konsolda:

text
ism: Ismingizni yozing.
odam: 1–20 kishi.

E'tibor bering, nima o'zgardi. Qoidalar — ma'lumot: oddiy massiv. Yangi maydon qo'shish uchun tekshir ga tegmaysiz — massivga bitta qator qo'shasiz. flatMap har qoidaning massivini bitta ro'yxatga yoyadi: bo'sh massivlar yo'qoladi, xatolar qoladi. Va natija — o'sha tanish shakl, ok + xatolar — 10-qism bilan mos.

Qachon qaysi biri:

Usul Qachon Misol
keyin zanjiri bosqichlar bir-biriga bog'liq JSON → tuzilma → qiymatlar
Qoidalar ro'yxati maydonlar mustaqil forma maydonlari

Amalda ikkalasi birga ishlaydi: avval jsonniOqi zanjirda, keyin keyin(tekshir(bronQoidalari)) — hamma maydon birdaniga.

Tekshirib ko'ring: bronQoidalari ga telefon qoidasini qo'shish uchun nima o'zgaradi?

Javob

Faqat massivga bitta qator: qoida("telefon", (t) => /^\+998\d{9}$/.test(t), "Telefon +998XXXXXXXXX."). tekshir va qoida o'zgarmaydi. Regex — Langarlar va so'z chegarasi va Quantifierlar darslaridan.

6. Asinxron kod va Result

6.1 Promise — tayyor Result

Promise'ga yangi ko'z bilan qarang. U ham ikki holatli quti: bajarildi (qiymat bilan) yoki rad etildi (sabab bilan). then — ozgartir/keyin ga, catch — xato relsiga o'xshaydi. Promise kombinatorlari darsidagi Promise.allSettled esa natijani deyarli Result shaklida beradi: { status: "fulfilled", value } yoki { status: "rejected", reason }.

Farqi: rad etilgan Promise ham await qatorida throw bo'ladi. Ya'ni try...catch siz u yana "yashirin" xatoga aylanadi.

6.2 fetch ni Result'ga aylantirish

fetch da xatoning ikki turi bor: tarmoq xatosi (throw) va HTTP xato (javob keladi, lekin ok: false). fetch asoslari darsida bu farqni ko'rgan edik. Ikkalasini bitta Result shakliga keltiramiz — «Bahor» mashq API'si bilan haqiqiy so'rov:

js
const muvaffaq = (qiymat) => ({ ok: true, qiymat });
const xatoli = (xato) => ({ ok: false, xato });

const API = "https://ilmhamroh.uz/api/mashq";

async function taomniOl(id) {
  try {
    const javob = await fetch(`${API}/menyu/${id}`);
    const malumot = await javob.json();
    if (!javob.ok) {
      return xatoli({ holat: javob.status, xabar: malumot.xato });
    }
    return muvaffaq(malumot);
  } catch {
    return xatoli({ holat: 0, xabar: "Tarmoq bilan aloqa yo'q" });
  }
}

const birinchi = await taomniOl(1);
const yoq = await taomniOl(999);

console.log(birinchi.ok, birinchi.qiymat?.nom); // true Osh
console.log(yoq.ok, yoq.xato);
// false { holat: 404, xabar: 'Taom topilmadi' }

taomniOl hech qachon xato tashlamaydi — u doim Result qaytaradi. Uni chaqirgan kod try...catch siz, faqat ok bilan ishlaydi. birinchi.qiymat?.nom dagi ?. — xavfsizlik uchun: ok false bo'lganda ham xato chiqmasin. Xato obyekti matn emas, { holat, xabar } — chaqiruvchi 404 ni tarmoq xatosidan ajrata oladi.

11-qismdagi api.js boshqa yo'lni tanlagan edi: ApiXatosi ni tashlash (API klient qatlami). Ikkalasi ham to'g'ri. Muhimi — bitta loyihada bitta qatlam bitta uslubda bo'lsin.

7. Qachon throw, qachon Result

Result throw ning o'rnini to'liq bosmaydi. Xato strategiyasi darsidagi ikki xil xatoni eslang:

  • Operatsion xato — kutilgan, dastur ishining bir qismi: noto'g'ri forma, 404, buzuq import fayli. Bunday joyda Result qulay: xato natijaning shaklida ko'rinadi va chaqiruvchi uni o'tkazib yubora olmaydi.
  • Dasturchi xatosi (bug) — undefined ga metod chaqirish, noto'g'ri argument turi. Bunda throw to'g'ri: dastur darhol va baland ovoz bilan to'xtashi kerak. Bug'ni Result'ga o'rash uni yashiradi.

Yana bir qoida: bitta funksiya ikkalasini aralashtirmasin. Result qaytaradigan funksiya ichidan kutilgan xato throw bo'lib chiqib ketsa, chaqiruvchi uni kutmagan bo'ladi. Uchinchi tomon kodini «Chegarada: throw ni Result'ga aylantirish» bo'limidagi xavfsiz bilan o'rang.

Result'ning bitta zaif joyi bor: ok ni tekshirishni hech kim majburlamaydi. natija.qiymat.nom ni tekshiruvsiz yozsangiz, xato holatida TypeError olasiz. TypeScript (15-qism) bu tekshiruvni majburiy qila oladi: ok ni tekshirmasdan qiymat ga yetib bo'lmaydi. Shuning uchun Result naqshi TypeScript loyihalarida ayniqsa mashhur.

8. Ko'p uchraydigan xatolar

8.1 ok ni tekshirmaslik

js
const xatoli = (xato) => ({ ok: false, xato });
const natija = xatoli("Taom topilmadi");

console.log(natija.qiymat.nom);
text
TypeError: Cannot read properties of undefined (reading 'nom')

Tarjimasi: "undefined ning xususiyatlarini o'qib bo'lmaydi (nom ni o'qishda)." Xatoli Result'da qiymat yo'q. Tuzatish: avval if (natija.ok), yoki zanjirni keyin/ozgartir bilan quring — ular tekshiruvni o'zi qiladi.

8.2 ozgartir va keyin ni adashtirish

Result qaytaradigan funksiyani ozgartir bilan ulasangiz — ichma-ich Result ({ ok: true, qiymat: { ok: false, ... } }). Xato "muvaffaqiyat" qutisiga yashirinadi. Tuzatish: funksiya Result qaytarsa — keyin, oddiy qiymat qaytarsa — ozgartir.

8.3 Bosqich ichidan throw

Zanjir bosqichi JSON.parse ni o'ramsiz chaqirsa, xato Result bo'lib emas, throw bo'lib chiqadi va butun zanjirni buzadi. Tuzatish: xato tashlashi mumkin bo'lgan har chaqiruvni chegarada — xavfsiz bilan — o'rang.

8.4 Xato ma'lumotini yo'qotish

xatoli("xato") yoki xatoli(false) — chaqiruvchi nima bo'lganini bilmaydi. Tuzatish: xatoga foydali ma'lumot qo'ying: o'quvchiga xabar, dasturga kod yoki maydon ({ maydon, xabar }, { holat, xabar }).

8.5 Bo'sh qiymatda ||

narx || "narx yo'q" — 0 ni ham "yo'q" qiladi. Tuzatish: ??.

9. Mashqlar

1-mashq (oson): Narxni o'qish

narxniOqi(matn) Result qaytarsin: matn musbat butun son bo'lsa — muvaffaq(son), aks holda xatoli("Narx musbat butun son bo'lsin."). Ishora: Number va Number.isInteger (Matn ↔ son).

js
console.log(narxniOqi("35000")); // { ok: true, qiymat: 35000 }
console.log(narxniOqi("-5").ok); // false
console.log(narxniOqi("abc").ok); // false
Yechim
js
const muvaffaq = (qiymat) => ({ ok: true, qiymat });
const xatoli = (xato) => ({ ok: false, xato });

const narxniOqi = (matn) => {
  const son = Number(matn);
  return Number.isInteger(son) && son > 0
    ? muvaffaq(son)
    : xatoli("Narx musbat butun son bo'lsin.");
};

console.log(narxniOqi("35000")); // { ok: true, qiymat: 35000 }
console.log(narxniOqi("-5").ok); // false
console.log(narxniOqi("abc").ok); // false

Number("abc") — NaN, Number.isInteger(NaN) — false. Bitta shart ikkala holatni ham ushladi. Funksiya hech qachon xato tashlamaydi — doim Result.

2-mashq (o'rta): Telefon — va'da bajariladi

Kompozitsiya darsining 3-mashqida telefonniMeyorla quvurini yozgan edik va "uzunlikni ham tekshirish kerak" degan edik. Endi shuni qiling: telefonniTekshir Result zanjiri bo'lsin. Bosqichlar: raqam bo'lmagan belgilarni o'chirish (ozgartir), 9 raqam bo'lsa 998 qo'shish (ozgartir), uzunlik aynan 12 ekanini tekshirish (keyin), + qo'shish (ozgartir). Boshlanishi — muvaffaq(matn).

Yechim
js
const pipe = (...bosqichlar) => (kirish) =>
  bosqichlar.reduce((natija, bosqich) => bosqich(natija), kirish);
const muvaffaq = (qiymat) => ({ ok: true, qiymat });
const xatoli = (xato) => ({ ok: false, xato });
const keyin = (fn) => (natija) =>
  natija.ok ? fn(natija.qiymat) : natija;
const ozgartir = (fn) => (natija) =>
  natija.ok ? muvaffaq(fn(natija.qiymat)) : natija;

const telefonniTekshir = pipe(
  muvaffaq,
  ozgartir((matn) => matn.replace(/\D/g, "")),
  ozgartir((r) => (r.length === 9 ? `998${r}` : r)),
  keyin((r) =>
    r.length === 12 ? muvaffaq(r) : xatoli(`${r.length} ta raqam`),
  ),
  ozgartir((r) => `+${r}`),
);

console.log(telefonniTekshir("90 000-00-00"));
console.log(telefonniTekshir("90 000 00"));

Konsolda:

text
{ ok: true, qiymat: '+998900000000' }
{ ok: false, xato: '7 ta raqam' }

Ikkinchi raqamda 7 ta raqam bor edi — 998 qo'shilmadi (9 emas), keyin ham 12 bo'lmadi. Xato relsiga tushgach, oxirgi ozgartir ishlamadi: + qo'shilmadi. pipe ning birinchi bosqichi muvaffaq — oddiy matnni Result qutisiga soladi.

3-mashq (qiyin): Qoidalar bilan ro'yxatdan o'tish formasi

«Funksional validatsiya» bo'limidagi qoida va tekshir ni ishlating. Ro'yxatdan o'tish formasi uchun qoidalar yozing: ism — kamida 2 belgi; email — @ va nuqta bor (/^[^@\s]+@[^@\s]+\.[^@\s]+$/); yosh — 16 dan 100 gacha. Ikkita formani tekshiring: biri to'g'ri, ikkinchisida uchala maydon xato.

Yechim
js
const muvaffaq = (qiymat) => ({ ok: true, qiymat });

const qoida = (maydon, togrimi, xabar) => (forma) =>
  togrimi(forma[maydon]) ? [] : [{ maydon, xabar }];

const tekshir = (qoidalar) => (forma) => {
  const xatolar = qoidalar.flatMap((q) => q(forma));
  return xatolar.length === 0
    ? muvaffaq(forma)
    : { ok: false, xatolar };
};

const emailNaqshi = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
const royxatdanOtish = tekshir([
  qoida("ism", (s) => s.trim().length >= 2, "Ism kamida 2 harf."),
  qoida("email", (s) => emailNaqshi.test(s), "Email noto'g'ri."),
  qoida("yosh", (n) => n >= 16 && n <= 100, "Yosh 16–100."),
]);

const togri = { ism: "Malika", email: "ali@example.com", yosh: 22 };
const notogri = { ism: "M", email: "malika@", yosh: 12 };

console.log(royxatdanOtish(togri).ok); // true
console.log(royxatdanOtish(notogri).xatolar.map((x) => x.maydon));
// [ 'ism', 'email', 'yosh' ]

Uch xato birdaniga chiqdi — mehmon formani bir marta tuzatadi. Email regex'i ataylab sodda: "bo'sh joysiz, @, nuqtali domen". Email'ning to'liq tekshiruvi — tasdiqlash xati yuborish; regex faqat aniq xatolarni ushlaydi.

4-mashq: Vazifalar qadami — paketniOqi Result bilan

vazifalar dagi paketniOqi(matn) buzuq importda VazifaXatosi tashlaydi (kod: "JSON", "TUZILMA", "VERSIYA"...). Kanon kodni o'zgartirmaymiz. kurs/mashqlar/12/27-result/import.mjs da xavfsiz ning takomillashgan varianti natijaga(funksiya) ni yozing: u xatoning message ini ham, kod ini ham saqlasin. Kanonni import qilmasdan sinash uchun soddalashtirilgan paketniOqi ni shu faylda yozing (faqat JSON va versiya tekshiruvi).

Yechim
js
const muvaffaq = (qiymat) => ({ ok: true, qiymat });
const xatoli = (xato) => ({ ok: false, xato });

const natijaga = (funksiya) => (...argumentlar) => {
  try {
    return muvaffaq(funksiya(...argumentlar));
  } catch (xato) {
    const kod = xato.kod ?? "NOMALUM";
    return xatoli({ kod, xabar: xato.message });
  }
};

class VazifaXatosi extends Error {
  constructor(xabar, { kod }) {
    super(xabar);
    this.kod = kod;
  }
}

function paketniOqi(matn) {
  let xom;
  try {
    xom = JSON.parse(matn);
  } catch {
    throw new VazifaXatosi("Matn JSON emas.", { kod: "JSON" });
  }
  if (xom.versiya !== 1) {
    throw new VazifaXatosi("Faqat 1-versiya.", { kod: "VERSIYA" });
  }
  return xom.vazifalar;
}

const importniOqi = natijaga(paketniOqi);

const matnlar = [
  '{"versiya":1,"vazifalar":[]}',
  "salom",
  '{"versiya":2}',
];
for (const matn of matnlar) {
  const natija = importniOqi(matn);
  const { kod, xabar } = natija.xato ?? {};
  console.log(natija.ok ? "OK" : `${kod}: ${xabar}`);
}

Konsolda:

text
OK
JSON: Matn JSON emas.
VERSIYA: Faqat 1-versiya.

natijaga — chegara. Kanon paketniOqi dagi aniq kod lar Result ichida saqlandi, shuning uchun interfeys "JSON" va "VERSIYA" holatlariga har xil xabar ko'rsata oladi. xato.kod ?? "NOMALUM" — kutilmagan xato (kod siz) uchun zaxira. Haqiqiy loyihada siz import { paketniOqi } from "./paket.js" qilasiz va natijaga ga berasiz.

bash
git add 12/27-result/import.mjs
git commit -m "12/27: import xatosini Result ga aylantirish"

10. Real ishda

Quyidagi kutubxona nomlarini hozir bilish shart emas — ular bugungi g'oyani tayyor holda beradi, xolos.

  • Kutubxonalar. TypeScript loyihalarida Result uchun neverthrow, to'liq FP uchun fp-ts va Effect kutubxonalari bor. Ularning map, andThen, match metodlari — bugungi ozgartir va keyin ning o'zi.
  • Forma kutubxonalari. Zod (sxema tekshiruvi) safeParse metodi bilan aynan { success: true, data } yoki { success: false, error } qaytaradi — Result shakli. React Hook Form va boshqalar shunga tayanadi.
  • Boshqa tillar. Rust'da Result<T, E> va Option<T> tilning asosi; Go'da funksiya qiymat, err juftligini qaytaradi; Swift va Kotlin'da optional turlar bor. JavaScript'dan keyin boshqa tilga o'tsangiz, bu g'oya sizni kutib turadi.
  • API javoblari. Ko'p REST API'lar javobni { ok, data, error } shaklida beradi — server tomonda ham Result naqshi.
  • Intervyu. "?? va || farqi?", "xatoni qachon tashlash, qachon qaytarish kerak?", "Promise.allSettled natijasi qanday?" — tez-tez so'raladi.

Xulosa

  • null xatoni kechiktiradi, throw esa funksiya nomidan ko'rinmaydi; Result xatoni qiymatning shakliga chiqaradi: { ok: true, qiymat } / { ok: false, xato }.
  • Maybe — "qiymat bor yoki yo'q" qutisi; JavaScript'da uning ishini ko'pincha ?. va ?? qiladi; bo'sh qiymatda || emas, ??.
  • ozgartir (oddiy qiymat) va keyin (Result qaytaradigan) — curried yordamchilar; ular bilan Result zanjiri pipe ga ulanadi va birinchi xatoda o'zi to'xtaydi.
  • Mustaqil maydonlar uchun — qoidalar ro'yxati va flatMap: hamma xato birdaniga.
  • throw qiladigan kod va fetch chegarada Result'ga aylantiriladi (xavfsiz, natijaga); bug'lar uchun esa throw qoladi.

Keyingi dars: JS dvigateli ichida — kodingiz qanday tahlil qilinadi, bytecode'ga aylanadi va "issiq" qismlari mashina kodiga kompilyatsiya qilinadi.

Manbalar

  • MDN: "Optional chaining (?.)", "Nullish coalescing operator (??)", "Promise.allSettled" — developer.mozilla.org
  • Scott Wlaschin, "Railway Oriented Programming" — fsharpforfunandprofit.com/rop
  • neverthrow — github.com/supermacro/neverthrow
  • Zod hujjatlari: safeParse — zod.dev
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
JavaScript'da Result va Maybe: xato va bo'sh qiymatlar bilan FP uslubida ishlash — IlmHamroh