IlmHamroh
JavaScript Full-stack/18-qism. React chuqur4/42-dars26 daqiqa
Mundarija (36)

use(): Promise va Context'ni o'qish — kesh, shartli chaqiruv va tuzoqlar

Qisqacha: use(promise) Promise tayyor bo'lsa qiymatini qaytaradi. Tayyor bo'lmasa — komponentni to'xtatadi va eng yaqin <Suspense> fallback'ini ko'rsatadi. Rad etilgan Promise'da esa xato tashlaydi. Asosiy qoida: Promise render ichida yaratilmaydi. U modul darajasida, hodisa ishlovchisida yoki keshda yaratiladi va bir xil so'rov uchun o'sha Promise qaytadi. Aks holda har render yangi Promise beradi va kutish abadiy davom etadi. use(Context) esa useContext ning o'zi, faqat if va sikl ichida ham chaqirsa bo'ladi. Ikkala holatda ham use faqat render paytida ishlaydi: hodisa ishlovchisida va try/catch ichida — yo'q.

Bu darsda

  • use(promise) ning uch holatini (kutish, qiymat, xato) jonli ko'rasiz va React Promise'ga qanday belgi qo'yishini konsolda kuzatasiz.
  • "Promise render ichida" tuzog'ini ko'rasiz: cheksiz kutish va React'ning haqiqiy ogohlantirishi.
  • Promise'ni to'g'ri joyda yaratasiz: hodisada, state'da yoki Map/WeakMap keshida.
  • Rad etilgan Promise'ni .catch bilan qiymatga aylantirasiz va nega use ni try/catch ga o'rab bo'lmasligini tushuntirasiz.
  • use(Context) ni shart va sikl ichida ishlatasiz va vazifalar dagi statistika qadamini commit qilasiz.

Oldin bilishingiz kerak: Suspense, Context I, Hooklar qoidalari, Promise asoslari.

1. Nega bu kerak?

Oldingi darsda hamma Promise modulning boshida, bir marta yaratildi: const menuPromise = new Promise(…). Haqiqiy ilovada esa so'rov nimagadir bog'liq: qaysi taom tanlangan, qaysi tab ochilgan, foydalanuvchi qachon tugmani bosgan.

Sardor taom sahifasini yozdi va eng tabiiy ko'ringan yo'lni tanladi:

jsx
function Dish({ id }) {
  const dish = use(fetchDish(id)); // ❌
  return <p>{dish.nom}</p>;
}

Sahifa ochildi — va abadiy "Yuklanmoqda…" da qoldi. Server javob berdi, konsolda xato yo'q, lekin taom chiqmadi. Bu darsda nega shunday bo'lganini va qanday tuzatilishini ko'ramiz.

Hayotiy o'xshatish: davlat xizmatlari markazidagi navbat taloni. Kirganda talon olasiz va tablo sizning raqamingizni chaqirishini kutasiz. Endi tasavvur qiling: tabloga har qaraganingizda yangi talon olasiz. Raqamingiz hech qachon chaqirilmaydi — har safar navbat oxiridasiz. use(fetchDish(id)) — aynan shu. To'g'ri yo'l — talonni bir marta olib, cho'ntakda saqlash. Kodda bu cho'ntakning nomi — kesh.

2. use(promise) qanday ishlaydi

2.1 Uch holat

Promise asoslari darsidan bilasiz: Promise uch holatdan birida bo'ladi. use har biriga o'zicha javob beradi:

Promise holati use(promise) Ekranda
pending (kutilmoqda) komponentni to'xtatadi eng yaqin <Suspense> fallback'i
fulfilled (bajarildi) qiymatni qaytaradi kontent
rejected (rad etildi) xatoni tashlaydi xato qutisi yoki error boundary

Uchinchi qatorga «Rad etilgan Promise» bo'limida qaytamiz. Avval ikkinchisini chuqurroq ko'ramiz.

2.2 React Promise'ga belgi qo'yadi

Promise'ning holatini JavaScript'da sinxron bilib bo'lmaydi: faqat .then orqali, keyinroq. Unda React tayyor Promise'ni qayta-qayta kutmaslikni qanday biladi? U Promise obyektining o'ziga ikki maydon yozib qo'yadi: status va value. Buni konsolda ko'ramiz. Promise 100 ms da tayyor bo'ladi, tugma esa 300 ms da bosiladi:

jsx
import { Suspense, use, useEffect, useState } from "react";

const pricePromise = new Promise((resolve) => {
  setTimeout(() => resolve(35000), 100);
});

function Price() {
  console.log("oldin:", pricePromise.status);
  const price = use(pricePromise);
  console.log("keyin:", pricePromise.status, pricePromise.value);
  return <p>Osh: {price} so'm</p>;
}

function Loading() {
  useEffect(() => console.log("fallback ekranga chiqdi"), []);
  return <p>Yuklanmoqda…</p>;
}

export default function App() {
  const [show, setShow] = useState(false);
  return (
    <div>
      <button onClick={() => setShow(true)}>Ko'rsatish</button>
      <Suspense fallback={<Loading />}>{show && <Price />}</Suspense>
    </div>
  );
}

Konsolda:

text
oldin: undefined
fallback ekranga chiqdi
oldin: fulfilled
keyin: fulfilled 35000

Ekranda:

text
Ko'rsatish
Osh: 35000 so'm

Ikki narsa ko'rinadi:

  • Birinchi duch kelganda Promise allaqachon tayyor edi, lekin status — undefined. React buni bilmagan, shuning uchun komponent baribir bir lahza to'xtadi va fallback chiqdi. React .then qo'shdi, javob kelgach status: "fulfilled" va value: 35000 ni yozdi.
  • Keyingi renderlarda use maydonga qaraydi va qiymatni darhol, to'xtamasdan qaytaradi.

Xulosa: o'sha Promise obyekti ikkinchi marta kelsa — qiymat darhol. Yangi obyekt esa har safar birinchi martadek: belgisi yo'q. Butun dars shu ikki gap atrofida.

Maslahat: status va value — React'ning ichki kelishuvi, o'z kodingizda ularga tayanmang. Biz ularni faqat mexanizmni ko'rish uchun o'qidik.

2.3 Promise hodisada tug'iladi

Eng toza yo'l — Promise'ni hodisa ishlovchisida yaratish va state'da saqlash. Hodisa bir marta ishlaydi — Promise ham bitta. Komponent esa uni props orqali olib, use bilan o'qiydi:

jsx
import { Suspense, use, useState } from "react";

function fetchPrices() {
  console.log("so'rov yuborildi");
  return new Promise((resolve) => {
    setTimeout(() => resolve({ osh: 35000, manti: 30000 }), 300);
  });
}

function Prices({ pricesPromise }) {
  const prices = use(pricesPromise);
  return (
    <p>
      Osh: {prices.osh} · Manti: {prices.manti}
    </p>
  );
}

export default function App() {
  const [pricesPromise, setPricesPromise] = useState(null);
  function handleClick() {
    setPricesPromise(fetchPrices()); // ✅ Promise — hodisada
  }
  return (
    <div>
      <button onClick={handleClick}>Narxlarni yuklash</button>
      <Suspense fallback={<p>Narxlar yuklanmoqda…</p>}>
        {pricesPromise && <Prices pricesPromise={pricesPromise} />}
      </Suspense>
    </div>
  );
}

Konsolda:

text
so'rov yuborildi

Ekranda (bosgandan 100 ms keyin):

text
Narxlarni yuklash
Narxlar yuklanmoqda…

O'sha kod, 500 ms kutib:

jsx
// yuqoridagi kod — o'zgarishsiz

Ekranda (bosgandan 500 ms keyin):

text
Narxlarni yuklash
Osh: 35000 · Manti: 30000

So'rov bir marta yuborildi, garchi Prices bir necha marta chizilgan bo'lsa ham. Promise state'da yashaydi: har render state'dan o'sha obyektni oladi. Tugmani qayta bossangiz — yangi so'rov, yangi Promise. Bu to'g'ri: foydalanuvchi yangilashni o'zi so'radi.

Tekshirib ko'ring: Nega fetchPrices() ni useState(fetchPrices()) deb boshlang'ich qiymatga yozish yomon fikr?

Javob

useState(fetchPrices()) da fetchPrices() har renderda chaqiriladi — React faqat birinchi natijani saqlaydi, qolganlarini tashlaydi. Har render — yangi so'rov, keraksiz tarmoq yuki. To'g'risi — dangasa boshlash: useState(() => fetchPrices()) (State I). Lekin komponent to'xtab, hali ekranga chiqmagan bo'lsa, React uning state'ini saqlamaydi. Keyingi urinishda boshlang'ich funksiya yana chaqiriladi. Shuning uchun Promise'ni to'xtaydigan komponentning tashqarisida saqlang.

3. Tuzoq: Promise render ichida

3.1 Cheksiz kutish

Endi Sardorning xatosini jonli ko'ramiz. fetchMenu har chaqirilganda yangi Promise yaratadi. Konsolni toza saqlash uchun faqat birinchi uchtasini va o'ninchisini yozamiz:

jsx
import { Suspense, use } from "react";

let count = 0;
function fetchMenu() {
  count++;
  if (count <= 3 || count === 10) console.log(`Promise #${count}`);
  return new Promise((resolve) => {
    setTimeout(() => resolve(["Osh", "Manti"]), 100);
  });
}

function Menu() {
  const dishes = use(fetchMenu()); // ❌ har renderda yangi Promise
  return <p>{dishes.join(", ")}</p>;
}

export default function App() {
  return (
    <Suspense fallback={<p>Yuklanmoqda…</p>}>
      <Menu />
    </Suspense>
  );
}

Konsolda (2,5 soniyadan keyin):

text
Promise #1
Promise #2
Promise #3
Promise #10

Ekranda:

text
Yuklanmoqda…

Har Promise 100 ms da hal bo'ladi, lekin menyu 2,5 soniyada ham chiqmadi. «Ishga tushir» natija oynasida u umuman chiqmaydi — oynani yopguncha yangi Promise'lar yaratilaveradi.

3.2 Nega halqa

Diagrammada halqaga qarang. Har aylanishda use qo'liga yangi, belgisi yo'q Promise tushadi:

flowchart TD
  A["React Menu ni chizadi"] --> B["fetchMenu(): YANGI Promise"]
  B --> C{"Promise'da belgi bormi?"}
  C -- "yo'q, hech qachon" --> D["Menu to'xtaydi, fallback"]
  D --> E["Promise hal bo'ldi"]
  E --> A
  C -. "kesh bo'lsa: ha" .-> F["Qiymat qaytadi"]
  1. Render: fetchMenu() → Promise #1, use uni bilmaydi → to'xtash.
  2. 100 ms: Promise #1 hal bo'ldi. React Menu ni boshidan qayta chizadi.
  3. Qayta render: fetchMenu() yana chaqirildi → Promise #2. U ham yangi, ham tayyor emas → yana to'xtash.

React "bu o'sha so'rov" deb taxmin qila olmaydi. Uning uchun ikki Promise — ikki xil obyekt, xuddi ikki talon kabi. Komponentni React istagan paytda qayta chaqiradi: to'xtagandan keyin, prewarm'da, StrictMode'da (Pure komponentlar). Shuning uchun render ichidagi har new Promise — yangi talon.

3.3 React'ning ogohlantirishi

Promise juda tez hal bo'lsa (masalan, Promise.resolve), React to'xtagan komponentni o'sha urinish ichida darhol qayta sinaydi. Shunda u Promise almashganini sezadi:

jsx
import { Suspense, use } from "react";

let count = 0;
function fetchMenu() {
  count++;
  console.log("yangi Promise #" + count);
  return Promise.resolve(["Osh", "Manti"]); // darhol tayyor
}

function Menu() {
  const dishes = use(fetchMenu()); // ❌ baribir har renderda yangi
  return <p>{dishes.join(", ")}</p>;
}

export default function App() {
  return (
    <Suspense fallback={<p>Yuklanmoqda…</p>}>
      <Menu />
    </Suspense>
  );
}

Konsolda:

text
yangi Promise #1
yangi Promise #2
yangi Promise #3
A component was suspended by an uncached promise. Creating promises inside a Client Component or hook is not yet supported, except via a Suspense-compatible library or framework.

Ekranda:

text
Osh, Manti

Tarjimasi: "Komponent keshlanmagan Promise tufayli to'xtadi. Client komponent yoki hook ichida Promise yaratish hozircha qo'llab-quvvatlanmaydi — faqat Suspense bilan ishlaydigan kutubxona yoki framework orqali." Bu yerda menyu chiqdi — React yangi Promise o'rniga eskisining qiymatini oldi. Lekin bu omad. Haqiqiy so'rov (100 ms) bo'lsa — oldingi bo'limdagi cheksiz halqa, ba'zan esa ogohlantirishsiz.

vazifalar kanonini qurishda xuddi shu xabar Chrome'da ham chiqdi. Statistika Promise'i vaqtincha keshsiz qilinib, npm run dev (StrictMode) da ochildi. Konsolda aynan o'sha matn chiqdi, panel esa baribir ochildi — hisob tez (setTimeout 0). Ogohlantirish — kod xato ekanining belgisi, natija to'g'ri chiqqan bo'lsa ham.

Tekshirib ko'ring: "Ekranda natija to'g'ri, faqat konsolda ogohlantirish — e'tibor bermasa ham bo'ladi" degan fikr nega xavfli?

Javob

Natija tezlikka bog'liq. Bugun hisob 1 ms da tugaydi va React eski Promise qiymatini olib ulguradi. Ertaga ma'lumot serverdan kelsa yoki ro'yxat kattalashsa — halqa paydo bo'ladi, sahifa abadiy "Yuklanmoqda…" da qoladi. Ogohlantirish — kelajakdagi xatoni bugun ko'rsatyapti.

4. Kesh: bitta kalit — bitta Promise

4.1 Map bilan kesh

Talonni cho'ntakda saqlaymiz. Kesh — kalit bo'yicha Promise'lar ombori: id birinchi marta so'ralganda Promise yaratiladi va saqlanadi. Keyingi safar — o'sha obyekt qaytadi:

jsx
import { Suspense, use, useState } from "react";

const DISHES = { 1: "Osh — 35000 so'm", 4: "Manti — 30000 so'm" };
const cache = new Map();

function fetchDish(id) {
  if (!cache.has(id)) {
    console.log("so'rov: taom", id);
    const promise = new Promise((resolve) => {
      setTimeout(() => resolve(DISHES[id]), 200);
    });
    cache.set(id, promise);
  }
  return cache.get(id);
}

function Dish({ id }) {
  const dish = use(fetchDish(id)); // ✅ bitta id — bitta Promise
  return <p>{dish}</p>;
}

export default function App() {
  const [id, setId] = useState(1);
  return (
    <div>
      <button onClick={() => setId(1)}>Osh</button>{" "}
      <button onClick={() => setId(4)}>Manti</button>
      <Suspense fallback={<p>Yuklanmoqda…</p>}>
        <Dish id={id} />
      </Suspense>
    </div>
  );
}

Konsolda:

text
so'rov: taom 1
so'rov: taom 4

Ekranda (oshga qaytgandan 100 ms keyin):

text
Osh Manti
Osh — 35000 so'm

Osh → manti → yana osh, lekin so'rov faqat ikkita. Oshga qaytganda fallback ham chiqmadi. Keshdagi Promise'da React'ning belgisi bor (fulfilled), shuning uchun use qiymatni darhol berdi. fetchDish(id) endi render ichida chaqirilsa ham xavfsiz: u Promise yaratmaydi, keshdan oladi.

Bosish Keshda So'rov Fallback
ochilish (id 1) 1 yo'q → yaratildi ha ha
«Manti» (id 4) 4 yo'q → yaratildi ha ha
«Osh» (id 1) 1 bor, fulfilled yo'q yo'q

4.2 Keshning narxi

Kesh bepul emas. Uch savol paydo bo'ladi:

  • Qachon eskiradi? Oshning narxi o'zgarsa, keshdagi Promise eski narxni abadiy beradi. Uni qachondir o'chirish (invalidatsiya) kerak.
  • Xotira. Map har kalitni sahifa yopilguncha ushlab turadi. Minglab kalit — minglab Promise.
  • Xato ham keshlanadi. Rad etilgan Promise keshda qolsa, "Qayta urinish" ham o'sha xatoni qaytaradi — uni keshdan olib tashlash kerak.

Bularni qo'lda yechish — butun bir soha. Shuning uchun real ilovada kesh kutubxonaga topshiriladi: TanStack Query (TanStack Query II: kesh hayot sikli) yoki framework. Ular ichida aynan bugungi g'oya bor: "bir kalit — bitta Promise". Bugungi Map — o'rganish va kichik holatlar uchun.

React'ning o'zida cache() funksiyasi ham bor, lekin u faqat server komponentlari uchun (React cache()). Brauzer kodida u keshlamaydi.

4.3 WeakMap: kalit — obyektning o'zi

Ba'zan kalit — raqam emas, obyekt: masalan, butun vazifalar massivi. Bunda WeakMap qulay. Obyekt boshqa hech qayerda ishlatilmay qolsa, uning yozuvi keshdan o'zi yo'qoladi. Bu vazifalar dagi statistika keshining asosi — «Vazifalar qadami» bo'limida ko'ramiz.

5. Rad etilgan Promise

5.1 use xatoni tashlaydi

Server 500 qaytardi — Promise rad etildi. use uni render ichida tashlaydi, xuddi komponentning o'zi throw qilgandek:

jsx
import { Suspense, use } from "react";

const menuPromise = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error("500: server xatosi")), 300);
});

function Menu() {
  const dishes = use(menuPromise);
  return <p>{dishes.join(", ")}</p>;
}

export default function App() {
  return (
    <Suspense fallback={<p>Yuklanmoqda…</p>}>
      <Menu />
    </Suspense>
  );
}

Konsolda:

text
Uncaught Error: 500: server xatosi

Natija oynasida — "Komponentni chizishda xato" qutisi, ilova yo'q. <Suspense> faqat kutishni ushlaydi, xatoni emas. Xatoni ushlaydigan chegara — error boundary (Error boundary, to'rt darsdan keyin). Ikkalasi odatda yonma-yon turadi: biri "kutilmoqda", ikkinchisi "xato bo'ldi".

5.2 Xatoni qiymatga aylantirish

Error boundary'siz ham yo'l bor: Promise'ning o'ziga .catch qo'shib, xatoni oddiy qiymatga aylantirish (Promise zanjiri). Shunda use hech qachon tashlamaydi. Komponent xatoni o'zi chizadi:

jsx
import { Suspense, use } from "react";

function fetchPrice(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error(`404: taom ${id} topilmadi`));
    }, 200);
  });
}

// xato — qiymatga aylanadi: use endi tashlamaydi
const pricePromise = fetchPrice(99).catch((err) => ({
  error: err.message,
}));

function Price() {
  const result = use(pricePromise);
  if (result.error) {
    return <p role="alert">Narx yo'q: {result.error}</p>;
  }
  return <p>Narx: {result} so'm</p>;
}

export default function App() {
  return (
    <Suspense fallback={<p>Yuklanmoqda…</p>}>
      <Price />
    </Suspense>
  );
}

Ekranda:

text
Narx yo'q: 404: taom 99 topilmadi

.catch — Promise'ni yaratgan joyda, komponentdan tashqarida. role="alert" — Malika xatoni darhol eshitadi. Qaysi biri yaxshi? Kutilgan xato (taom topilmadi) — .catch bilan qiymat. Kutilmagan (server yiqildi) — error boundary.

5.3 try/catch ichida use — yo'q

Tabiiy fikr: "Xatoni ushlash kerakmi? try ga o'rayman". Ko'ring. Promise xatosiz hal bo'ladi — demak catch umuman ishlamasligi kerak edi. Konsolda ushlandi: qatori bor-yo'qligiga qarang:

jsx
import { Suspense, use } from "react";

const dishPromise = new Promise((resolve) => {
  setTimeout(() => resolve("Osh"), 300);
});

function Menu() {
  let name;
  try {
    name = use(dishPromise); // ❌ try ichida
  } catch (err) {
    console.log("ushlandi:", err.message);
    name = "xato";
  }
  return <p>{name}</p>;
}

export default function App() {
  return (
    <Suspense fallback={<p>Yuklanmoqda…</p>}>
      <Menu />
    </Suspense>
  );
}

Konsolda:

text
ushlandi: Suspense Exception: This is not a real error! It's an implementation detail of `use` to interrupt the current render. You must either rethrow it immediately, or move the `use` call outside of the `try/catch` block. Capturing without rethrowing will lead to unexpected behavior.
To handle async errors, wrap your component in an error boundary, or call the promise's `.catch` method and pass the result to `use`.
`use` was called from inside a try/catch block. This is not allowed and can lead to unexpected behavior. To handle errors triggered by `use`, wrap your component in a error boundary.

Ekranda:

text
xato

Promise 300 ms da "Osh" bilan muvaffaqiyatli hal bo'ldi — ekranda esa "xato". Sabab: use komponentni to'xtatish uchun ichkarida maxsus "xato" tashlaydi. catch uni tutib oldi va React to'xtashni bilmay qoldi. Xabarlar tarjimasi:

  • "Suspense Exception: bu haqiqiy xato emas! Bu use ning joriy renderni to'xtatish usuli. Uni darhol qayta tashlang yoki use ni try/catch dan tashqariga chiqaring."
  • "Asinxron xatolar uchun komponentni error boundary'ga o'rang yoki Promise'ning .catch metodini chaqirib, natijasini use ga bering."
  • "use try/catch ichida chaqirildi. Bu ruxsat etilmagan."

React'ning o'zi ikki yo'lni aytib turibdi — ikkalasi ham bizga tanish: error boundary va .catch.

6. use(Context)

6.1 Shart, sikl va erta return dan keyin

Context I darsida use(LangContext) ni if dan keyin chaqirgan edik. Oddiy hooklar bunday qila olmaydi (Hooklar qoidalari): React ularni chaqiruv tartibi bo'yicha taniydi. use esa hech narsani "kataklarda" saqlamaydi — u faqat o'qiydi. Shuning uchun tartib unga muhim emas:

jsx
import { createContext, use } from "react";

const CurrencyContext = createContext("so'm");

function PriceList({ items }) {
  if (items.length === 0) return <p>Menyu bo'sh</p>;
  const currency = use(CurrencyContext); // ✅ erta return'dan keyin
  return (
    <ul>
      {items.map((item) => (
        <li key={item.name}>
          {item.name} — {item.price} {currency}
        </li>
      ))}
    </ul>
  );
}

export default function App() {
  return (
    <CurrencyContext value="so'm">
      <PriceList items={[]} />
      <PriceList items={[{ name: "Osh", price: 35000 }]} />
    </CurrencyContext>
  );
}

Ekranda:

text
Menyu bo'sh
Osh — 35000 so'm

Birinchi PriceList Context'ni umuman o'qimadi, ikkinchisi — o'qidi. Bir xil komponent, har xil yo'l. useContext bilan bu react-hooks/rules-of-hooks lint xatosi bo'lardi. Xuddi shunday, use ni for sikli ichida ham chaqirish mumkin — masalan, bir nechta Context'ni ro'yxat bo'yicha o'qish.

Foyda qayerda? Context qimmat bo'lmasa ham, mantiq tozaroq: "ro'yxat bo'sh bo'lsa — hech narsa kerak emas" degan qoida birinchi qatorda turadi.

6.2 Faqat render paytida

use qayerda chaqirilsa bo'ladi — komponent yoki custom hook tanasida. Hodisa ishlovchisi esa render emas, u bosilganda ishlaydi:

jsx
import { createContext, use } from "react";

const CurrencyContext = createContext("so'm");

export default function App() {
  function handleClick() {
    const currency = use(CurrencyContext); // ❌ hodisa ichida
    console.log("Osh: 35000", currency);
  }
  return <button onClick={handleClick}>Narxni ko'rsat</button>;
}

Konsolda:

text
Uncaught Error: Context can only be read while React is rendering. In classes, you can read it in the render method or getDerivedStateFromProps. In function components, you can read it directly in the function body, but not inside Hooks like useReducer() or useMemo().
Error: Context can only be read while React is rendering. In classes, you can read it in the render method or getDerivedStateFromProps. In function components, you can read it directly in the function body, but not inside Hooks like useReducer() or useMemo().

"Context faqat React chizayotgan paytda o'qiladi. Funksiya komponentda — to'g'ridan-to'g'ri tanasida, lekin useReducer() yoki useMemo() kabi hooklar ichida emas." Xabarning o'rtasidagi "class" haqidagi gap — eski class komponentlar uchun (Class komponentlar). Tuzatish: const currency = use(CurrencyContext); ni komponent tanasiga ko'chiring, handleClick esa tayyor currency ni ishlatsin (closure).

6.3 Qoidalar jadvali

Qayerda Oddiy hook (useState, useContext) use
komponent tanasi, yuqori daraja
if ichida, erta return dan keyin
for sikl ichida
try/catch ichida
hodisa ishlovchisi, effekt

Qisqasi: use tartibdan ozod, lekin render vaqtidan ozod emas. try/catch esa hamma hook uchun taqiq: oddiy hooklarni ham React qoidalari unga qo'ymaslikni so'raydi.

Tekshirib ko'ring: use(Context) ni useMemo(() => use(CurrencyContext), []) ichida chaqirsa bo'ladimi?

Javob

Yo'q. useMemo ning funksiyasi render paytida ishlasa ham, xato matnining o'zi aytadi: "not inside Hooks like useReducer() or useMemo()". Context'ni tanada o'qing, natijasini useMemo ga bog'liqlik sifatida bering.

7. Serverdan uzatilgan Promise

use(promise) ning eng kuchli joyi — server bilan ishlaydigan framework'lar. Hozircha g'oyaning o'zini bilish yetarli, kodini Streaming va Suspense darsida yozamiz.

Next.js'da server komponent (serverda ishlaydigan komponent) so'rovni boshlaydi, lekin javobni kutmaydi. U Promise'ni props sifatida brauzerdagi komponentga beradi:

jsx
// Server komponent: so'rov serverda boshlanadi, kutilmaydi
export default function DishPage() {
  const reviewsPromise = getReviews(1);
  return (
    <Suspense fallback={<p>Sharhlar yuklanmoqda…</p>}>
      <Reviews reviewsPromise={reviewsPromise} />
    </Suspense>
  );
}

// Client komponent (alohida fayl, "use client"): brauzerda o'qiydi
function Reviews({ reviewsPromise }) {
  const reviews = use(reviewsPromise);
  return <p>{reviews.length} ta sharh</p>;
}

Sahifa HTML'i darhol keladi — sharhlar o'rnida fallback. Sharhlar tayyor bo'lgach, server ularni o'sha javobning davomida jo'natadi. Bu — "streaming". Bugungi qoida bu yerda ham bajariladi: Promise render ichida emas, serverda bir marta yaratilgan va uzatilgan. Brauzer komponenti uni faqat o'qiydi.

8. Vazifalar qadami: batafsil statistika (2-qism)

8.1 statistika-vadasi.ts — kesh

Oldingi darsda panel va chegarani qo'shdik, statistika-vadasi.ts ni esa ochmasdan ko'chirdik. Endi uni o'qiymiz. Kod kanondan aynan, izohlar qisqa qatorlarga bo'lingan:

ts
// statistika-vadasi.ts — batafsil statistika Promise'i (18/#04).
// use() Promise'ni kutadi, lekin uni RENDERDA YANGIDAN yaratib
// bo'lmaydi: har render yangi Promise bersa, React har safar qayta
// to'xtaydi (cheksiz kutish). Shuning uchun — kesh: bir ro'yxat
// (massiv havolasi) — bitta Promise. Ro'yxat o'zgarmasa (reducer
// o'sha massivni qaytaradi) — kesh ishlaydi; WeakMap — eski ro'yxat
// unutilsa, Promise ham xotiradan ketadi
import { batafsilHisobla } from "./batafsil-statistika.ts";
import type { BatafsilStatistika } from "./batafsil-statistika.ts";
import type { VazifaMalumoti } from "./turlar.ts";

type Vazifalar = readonly VazifaMalumoti[];

const kesh = new WeakMap<Vazifalar, Promise<BatafsilStatistika>>();

// Hisob — keyingi vazifada (setTimeout): bosish hodisasi tugab,
// React "kutilmoqda" holatini chizib ulgursin. Hisobning o'zi hali
// asosiy oqimda — 18/#24 da Web Worker'ga ko'chadi (shu funksiya
// almashadi)
function hisobla(vazifalar: Vazifalar): Promise<BatafsilStatistika> {
  return new Promise((hal) => {
    setTimeout(() => hal(batafsilHisobla(vazifalar)), 0);
  });
}

export function statistikaVadasi(
  vazifalar: Vazifalar,
): Promise<BatafsilStatistika> {
  let vada = kesh.get(vazifalar);
  if (vada === undefined) {
    vada = hisobla(vazifalar);
    kesh.set(vazifalar, vada);
  }
  return vada;
}

Bugungi darsning hamma qoidasi shu faylda:

  • Kalit — massivning o'zi (havola). Immutable yangilash tufayli ro'yxat o'zgarmasa, reducer o'sha massivni qaytaradi — demak o'sha Promise. Katak bosilsa — yangi massiv, yangi hisob.
  • WeakMap — eski ro'yxat hech qayerda qolmasa, uning Promise'i ham xotiradan ketadi. Map bo'lganda har katak bosish keshda bitta "o'lik" yozuv qoldirardi.
  • setTimeout(…, 0) — hisob darhol emas, keyingi vazifada. Bosish hodisasi tugaydi, React "Hisoblanmoqda…" ni chizib ulguradi (Event loop).
  • hisobla — almashtiriladigan qism. Kelajakda hisob Web Worker'ga ko'chadi (Og'ir ishni UI'dan ajratish). statistikaVadasi ning tashqi ko'rinishi va BatafsilStatistika o'zgarmaydi.

BatafsilStatistika.tsx da — bitta muhim qator:

tsx
  const s = use(statistikaVadasi(vazifalar));

Bu Sardorning use(fetchDish(id)) iga juda o'xshaydi. Farqi bitta: statistikaVadasi Promise yaratmaydi, keshdan oladi — yangisini faqat yangi ro'yxat uchun. Komponentda isLoading, useEffect, useState yo'q — faqat tayyor natija chiziladi.

8.2 Test va topilgan tuzoq

Panel uchun Vitest testi qo'shildi: tekshiruv/StatistikaPaneli.test.tsx. U soxta taymerlar bilan transition'ni qadamma-qadam tekshiradi. Bosilgandan keyin panel yopiq, fallback yo'q, holat "Hisoblanmoqda…". Taymerlar o'tkazilgach — jadval, umumiy satr 2 ta vazifa, 1 tasi bajarilgan (50%). O'rtacha uzunlik — 14 belgi.

Testni yozishda haqiqiy tuzoq chiqdi. Bosish act(…) ichida, lekin act kutilmagan (await siz) edi. Komponent transition ichida to'xtadi va React shunday dedi (xabarning birinchi gapi):

text
A component suspended inside an `act` scope, but the `act` call was not awaited.

"Komponent act doirasida to'xtadi, lekin act chaqiruvi kutilmadi." React bunday holatda to'xtagan komponentni qayta sinamaydi — test abadiy yopiq panelni ko'radi. Davosi — await act(async () => { fireEvent.click(tugma); }) va soxta taymerlarni ham await act ichida o'tkazish. Testlar chuqur — RTL II: async darsida.

8.3 Commit va tekshiruvlar

Ikki darsdagi ish — kanonda bitta commit:

bash
git add -A
git commit

Commit (kanon): feat: batafsil statistika — Suspense, use(), useTransition bilan ochish. Body: batafsil-statistika.ts (sof: teglar jami/bajarilgan, ko'p uchragan so'zlar, foiz, o'rtacha uzunlik), statistika-vadasi.ts (kesh: bir ro'yxat — bitta Promise, WeakMap), BatafsilStatistika — use(vada), StatistikaPaneli — useTransition + useDeferredValue + oldindan turgan <Suspense>.

Tekshiruvlar (haqiqiy):

  • npm test — node:test ℹ tests 160 · ℹ pass 160 (+4), Vitest Tests 31 passed (31) (+1, panel testi).
  • npm run lint, format:check, tip — exit 0. Build: dist/assets/index--gAptEZQ.js 253.53 kB │ gzip: 80.38 kB (oldin 79.60 kB — statistika +0.8 kB).
  • Brauzer tekshiruvi — 159/159. Bir marta 158 chiqdi — bu Suspense'ga aloqasiz poyga holati edi, sababi keyinroq o'lchab tuzatildi. Yangi panel tekshiruvi (e2e-18) — 12/12.
  • Diff: 9 fayl, +384 −3.

Hash (-gAptEZQ) sizda boshqa chiqadi — u fayl tarkibidan hisoblanadi.

9. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
abadiy "Yuklanmoqda…", Promise'lar ko'payadi Promise render ichida yaratilgan modul, hodisa yoki kesh
A component was suspended by an uncached promise o'sha xato, tez Promise bilan kesh: bir kalit — bitta Promise
oq ekran, Uncaught Error Promise rad etildi, error boundary yo'q .catch → qiymat yoki Error boundary
Suspense Exception: This is not a real error! use try/catch ichida try ni olib tashlang, .catch ishlating
Context can only be read while React is rendering use hodisa ishlovchisida use ni komponent tanasiga ko'chiring
narx o'zgardi, ekranda eskisi keshdagi Promise eskirgan keshdan o'chiring yoki kutubxona ishlating

10. Mashqlar

Mashqlar kurs/mashqlar/18/04-use/ da. Har yechimni «Ishga tushir» bilan sinang.

1-mashq (oson): mumkinmi?

ha yoki yo'q deb yozing.

  • use(ThemeContext) ni if (show) { … } ichida chaqirish mumkinmi?
  • use(promise) ni try { … } catch { … } ichida chaqirish mumkinmi?
  • use(ThemeContext) ni onClick ishlovchisi ichida chaqirish mumkinmi?
  • use(fetchDish(id)) to'g'ri, agar fetchDish Promise'ni keshdan qaytarsa?
Yechim
  • ha — use chaqiruv tartibiga bog'liq emas («Shart, sikl va erta return dan keyin» bo'limi).
  • yo'q — catch React'ning "Suspense Exception" ini tutib oladi, to'xtash buziladi.
  • yo'q — use faqat render paytida: "Context can only be read while React is rendering".
  • ha — muhimi chaqiruv qayerdaligi emas, har safar o'sha Promise qaytishi. Keshli funksiya renderda chaqirilsa ham yangi Promise yaratmaydi.

2-mashq (o'rta): talonni cho'ntakka soling

Kategoriya menyusi abadiy "Yuklanmoqda…" da. Sababi — fetchMenu har renderda yangi Promise beradi. Map kesh qo'shing: har kategoriya uchun Promise bir marta yaratilsin, so'rov: logi har kategoriya uchun bir marta chiqsin. Ishora: «Map bilan kesh» bo'limidagi fetchDish.

jsx
import { Suspense, use } from "react";

const MENU = { taom: "Osh, Lag'mon, Manti", ichimlik: "Ko'k choy" };
let count = 0;

function fetchMenu(category) {
  count++;
  if (count <= 2) console.log("so'rov:", category);
  return new Promise((resolve) => {
    setTimeout(() => resolve(MENU[category]), 150);
  });
}

function Menu({ category }) {
  return <p>{use(fetchMenu(category))}</p>;
}

export default function App() {
  return (
    <Suspense fallback={<p>Yuklanmoqda…</p>}>
      <Menu category="taom" />
    </Suspense>
  );
}

Konsolda:

text
so'rov: taom
so'rov: taom

Ekranda:

text
Yuklanmoqda…
Yechim
jsx
import { Suspense, use } from "react";

const MENU = { taom: "Osh, Lag'mon, Manti", ichimlik: "Ko'k choy" };
const cache = new Map();

function fetchMenu(category) {
  if (!cache.has(category)) {
    console.log("so'rov:", category);
    const promise = new Promise((resolve) => {
      setTimeout(() => resolve(MENU[category]), 150);
    });
    cache.set(category, promise);
  }
  return cache.get(category);
}

function Menu({ category }) {
  return <p>{use(fetchMenu(category))}</p>;
}

export default function App() {
  return (
    <Suspense fallback={<p>Yuklanmoqda…</p>}>
      <Menu category="taom" />
    </Suspense>
  );
}

Konsolda:

text
so'rov: taom

Ekranda:

text
Osh, Lag'mon, Manti

Menu baribir bir necha marta chizildi (urinish, prewarm, tayyor bo'lgach), lekin fetchMenu faqat birinchi chaqiruvda Promise yaratdi. Qolganlarida keshdan o'sha obyektni berdi. 150 ms da u hal bo'ldi va menyu chiqdi. Boshlang'ich koddagi count endi kerak emas — konsolni cheklash uchun edi.

3-mashq (qiyin): tugma bilan yuklash va xato

Ikki tugma: «Osh» va «Taom 99». Bosilganda narx serverdan olinadi (fetchPrice tayyor — o'zgartirmang). Talablar:

  1. Promise hodisa ishlovchisida yaratilsin va state'da saqlansin. Price komponenti uni props'dan olib, use bilan o'qisin.
  2. Kutish paytida <Suspense> fallback'i: "Yuklanmoqda…".
  3. Taom topilmasa ilova yiqilmasin: role="alert" li <p> da xato matni chiqsin. Ishora: «Xatoni qiymatga aylantirish» bo'limi — .catch Promise'ni yaratgan joyda.
jsx
const PRICES = { 1: 35000, 4: 30000 };

function fetchPrice(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (PRICES[id]) resolve(PRICES[id]);
      else reject(new Error(`404: taom ${id} topilmadi`));
    }, 200);
  });
}
Yechim
jsx
import { Suspense, use, useState } from "react";

const PRICES = { 1: 35000, 4: 30000 };

function fetchPrice(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (PRICES[id]) resolve(PRICES[id]);
      else reject(new Error(`404: taom ${id} topilmadi`));
    }, 200);
  });
}

function Price({ pricePromise }) {
  const result = use(pricePromise);
  if (result.error) return <p role="alert">{result.error}</p>;
  return <p>Narx: {result} so'm</p>;
}

export default function App() {
  const [pricePromise, setPricePromise] = useState(null);
  function load(id) {
    const promise = fetchPrice(id).catch((err) => ({
      error: err.message,
    }));
    setPricePromise(promise);
  }
  return (
    <div>
      <button onClick={() => load(1)}>Osh</button>{" "}
      <button onClick={() => load(99)}>Taom 99</button>
      <Suspense fallback={<p>Yuklanmoqda…</p>}>
        {pricePromise && <Price pricePromise={pricePromise} />}
      </Suspense>
    </div>
  );
}

Ekranda («Taom 99» dan 400 ms keyin):

text
Osh Taom 99
404: taom 99 topilmadi

«Osh» bosilsa — Narx: 35000 so'm. Promise har bosishda bir marta yaratiladi va state'da turadi — har render o'sha obyektni oladi. .catch rad etilishni { error } obyektiga aylantiradi, shuning uchun use tashlamaydi. Tugmalar ketma-ket bosilsa-chi? Har bosish yangi Promise'ni state'ga yozadi. React eng oxirgisini ko'rsatadi — useEffect III dagi poyga muammosi bu yerda o'z-o'zidan yo'q. Tugmani transition bilan o'rasangiz (Suspense), eski narx ham kutish paytida joyida qoladi.

4-mashq: Vazifalar qadami

feat/batafsil-statistika branch'ida statistika-vadasi.ts ni «Vazifalar qadami» bo'limi bo'yicha o'qib chiqing va panel testini qo'shing. Keyin commit qiling va PR oching.

Yechim

Tekshiruv ro'yxati:

  • npm test — node 160, Vitest 31. npm run lint, format:check, tip — toza.
  • Tajriba (keyin qaytaring): statistikaVadasi ichida keshni chetlab, har safar hisobla(vazifalar) qaytaring va npm run dev da panelni oching. Konsolda A component was suspended by an uncached promise… chiqadi. Keshni qaytaring — ogohlantirish yo'qoladi.
  • npm run build — boshlang'ich JS gzip ~80 kB, npm run preview da panel ochiladi, fallback ko'rinmaydi.
  • Commit — feat: batafsil statistika — Suspense, use(), useTransition bilan ochish. PR'ni birlashtiring.

11. Real ishda

  • TanStack Query, SWR, Relay — ichida "bir kalit — bitta Promise" keshi bor. useSuspenseQuery aslida keshdan Promise olib, React'ga beradi (TanStack Query I). Bugungi Map — ularning kichik modeli.
  • Next.js — server komponent Promise'ni boshlaydi, client komponent use bilan o'qiydi (Streaming va Suspense). Bu — use(promise) ning asosiy "uyi".
  • use(Context) — yangi kodda useContext o'rnida tobora ko'p uchraydi, kutubxonalar ham o'tyapti. Ikkalasi ham ishlaydi.
  • Kod ko'rigi (code review) da birinchi savol: "Bu Promise qayerda yaratilgan?". Render ichida bo'lsa — darhol belgi.
  • Intervyu savollari: "use va useContext farqi?", "nega use ni shart ichida chaqirsa bo'ladi?", "nega Promise'ni render ichida yaratib bo'lmaydi?", "use rad etilgan Promise bilan nima qiladi?".

Xulosa

  • use(promise): pending — to'xtatadi, fulfilled — qiymat, rejected — xato tashlaydi.
  • React Promise'ga status/value belgisini qo'yadi. O'sha obyekt ikkinchi marta — darhol, yangi obyekt — har safar kutish.
  • Promise render ichida yaratilmaydi: modul, hodisa (+ state) yoki kesh — "bir kalit, bitta Promise".
  • Rad etilish: .catch bilan qiymatga aylantiring yoki error boundary qo'ying. try/catch ichida use — yo'q.
  • use(Context) — if, sikl va erta return dan keyin ham, lekin faqat render paytida.
  • vazifalar: statistika-vadasi.ts — WeakMap keshi, kalit — ro'yxat massivi. Node 160, Vitest 31, brauzer 159/159.

Keyingi dars: Actions: async transitionlar va form action — transition ichidagi async funksiya, <form action> va React o'zi boshqaradigan "kutilmoqda" holati.

Manbalar

  • React: use — react.dev/reference/react/use (2026-10 holatiga, React 19.3)
  • React: <Suspense> — "Showing stale content while fresh content is loading" — react.dev/reference/react/Suspense
  • React: useContext — react.dev/reference/react/useContext
  • MDN: WeakMap — developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WeakMap
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
use(): Promise va Context'ni o'qish — kesh, shartli chaqiruv va tuzoqlar — IlmHamroh