IlmHamroh
JavaScript Full-stack/11-qism. Asinxron JavaScript va tarmoq28/32-dars20 daqiqa
Mundarija (38)

IndexedDB: brauzerdagi ma'lumotlar bazasi va xotira kvotasi

Qisqacha: IndexedDB — brauzer ichidagi asinxron ma'lumotlar bazasi: obyektlarni (satrga aylantirmasdan) saqlaydi, yuzlab megabaytni ko'taradi va indeks bo'yicha tez qidiradi. Ish tartibi: indexedDB.open("bahor", 1) → upgradeneeded da omborlar yaratiladi → har o'qish va yozish tranzaksiya ichida. Xom API hodisalarga qurilgan va noqulay, shuning uchun amalda u Promise'ga o'raladi yoki idb kutubxonasi ishlatiladi. Qancha joy borligini navigator.storage.estimate() aytadi.

Bu darsda

  • IndexedDB tushunchalarini — baza, object store, kalit, indeks, tranzaksiya, versiya — bir-biriga bog'lab tushuntira olasiz.
  • Bazani ochasiz, ma'lumot yozasiz, kalit va indeks bo'yicha o'qiysiz, o'chirasiz.
  • Hodisaga qurilgan API'ni Promise'ga o'rab, await bilan ishlatasiz.
  • Versiya oshganda bazani buzmasdan yangilaysiz (migratsiya).
  • idb kutubxonasi, navigator.storage.estimate/persist va OPFS bilan tanishasiz.

Oldin bilishingiz kerak: localStorage va sessionStorage, async / await, Promise asoslari, Event loop.

1. Nega bu kerak?

«Bahor» oshxonasi telefon ilovasiga o'xshash sayt xohlaydi: menyu rasmlari bilan, buyurtmalar tarixi bilan va internet yo'q joyda ham ochiladigan. Metroda, qishloqda, Wi-Fi uzilganda — mehmon baribir menyuni ko'rishi kerak.

O'tgan ikki darsdagi omborlar bunga yaramaydi:

  • localStorage — taxminan 5 MB, faqat satr va sinxron: katta ma'lumotni yozish sahifani qotiradi.
  • Cookie — 4 KB va har so'rov bilan serverga ketadi.

Kerakli narsa — ko'p ma'lumot sig'adigan, obyektlarni o'zicha saqlaydigan, qidira oladigan va sahifani qotirmaydigan ombor. Bu — IndexedDB. Brauzer xotirasi darsidagi o'xshatishda u katta omborxona edi.

Siz ishlatadigan ilovalardan misol: Telegram Web xabarlar va rasmlarni, Google Docs esa oflayn tahrirlash uchun hujjatlarni shu yerda ushlaydi.

Maslahat: Bu darsdagi kod natija oynasida ishlamaydi: natija xavfsizlik "qutisi"da, brauzer u yerda IndexedDB'ni taqiqlaydi (SecurityError). Sinash tartibi: VS Code'da bo'sh index.html → Live Server → F12 → Console. Bloklarni konsolga ketma-ket qo'ying: konsol eng yuqori darajadagi await ni tushunadi va oldingi blokdagi funksiyalarni eslab qoladi. Hamma "Konsolda:" natijalari Chrome 154 da shu tartibda tekshirilgan.

2. IndexedDB tuzilishi

2.1 To'rtta asosiy tushuncha

Avval xaritani ko'ramiz, keyin kod yozamiz:

flowchart TD
  O["Origin: http://127.0.0.1:5500"] --> B["Baza: bahor, versiya 2"]
  B --> T["Ombor: taomlar (kalit: id)"]
  B --> R["Ombor: bronlar (kalit: avtomatik)"]
  T --> Y1["1 → {id: 1, nom: 'Osh', ...}"]
  T --> Y2["2 → {id: 2, nom: 'Lag'mon', ...}"]
  T --> I["Indeks: turkum"]
  I --> I1["'ichimlik' → 4"]
  I --> I2["'taom' → 1, 2, 3"]

Nimaga qarang: bir origin'da bir nechta baza bo'lishi mumkin. Bazada — omborlar, omborda — yozuvlar, indeks esa yozuvlarni boshqa maydon bo'yicha tez topish uchun.

  • Baza (database) — nomi va versiya raqami bor. Har origin'ning bazalari alohida — localStorage dagi kabi.
  • Object store (obyekt ombori) — SQL'dagi jadvalga o'xshash. Unda obyektlar saqlanadi. Har yozuvning takrorlanmas kaliti bor: obyektning o'z maydoni (keyPath: "id") yoki avtomatik 1, 2, 3 ... (autoIncrement: true).
  • Indeks (index) — kalitdan boshqa maydon bo'yicha qidiruv. Kitob oxiridagi "atamalar ko'rsatkichi" kabi: "turkum" so'zini qidirsangiz, qaysi sahifalarda ekanini darhol aytadi.
  • Tranzaksiya (transaction) — bir yoki bir necha amalni "hammasi yoki hech biri" qilib bajarish. Pul o'tkazmasini eslang: bir hisobdan yechildi-yu, ikkinchisiga tushmay qolsa — bu fojia. Tranzaksiya ichidagi biror amal xato bersa, hammasi bekor bo'ladi.

2.2 Asinxron, lekin Promise emas

IndexedDB 2010-yillar boshida yaratilgan — Promise tilga kirmasdan oldin. Shuning uchun har amal darhol so'rov (request) obyektini qaytaradi. Bu tarmoq so'rovi emas — "buyurtma cheki" kabi narsa: buyurtma berildi, tayyor bo'lganda chaqirishadi. Natija keyinroq uning success yoki error hodisasida keladi — Callbacklar darsidagi uslub.

Bu noqulay, lekin tuzatish oson. Har so'rovni Promise'ga o'raydigan kichik yordamchi yozamiz va keyin hamma joyda await ishlatamiz:

js
function sorovniKut(sorov) {
  return new Promise((resolve, reject) => {
    sorov.onsuccess = () => resolve(sorov.result);
    sorov.onerror = () => reject(sorov.error);
  });
}

Bu — Promise yordamchilari va promisify darsidagi "callback API'ni Promise'ga o'rash" usuli.

3. Bazani ochish va omborlar yaratish

3.1 indexedDB.open va upgradeneeded

js
function bazaniOch() {
  const sorov = indexedDB.open("bahor", 1);
  sorov.onupgradeneeded = (event) => {
    const { oldVersion, newVersion } = event;
    console.log(`Yangilash: ${oldVersion} → ${newVersion}`);
    const db = sorov.result;
    const taomlar = db.createObjectStore("taomlar", {
      keyPath: "id",
    });
    taomlar.createIndex("turkum", "turkum");
  };
  return sorovniKut(sorov);
}

const db = await bazaniOch();
console.log(db.name, db.version, [...db.objectStoreNames].join(", "));

Konsolda (birinchi marta):

text
Yangilash: 0 → 1
bahor 1 taomlar

Ikkinchi marta (sahifani yangilab) ishga tushirsangiz — faqat bahor 1 taomlar. Nega?

open("bahor", 1) — "1-versiyali bahor bazasini och". Bunday baza yo'q bo'lsa yoki versiyasi kichikroq bo'lsa, brauzer upgradeneeded hodisasini chiqaradi. Omborlar va indekslar faqat shu hodisa ichida yaratiladi yoki o'zgartiriladi. Baza allaqachon 1-versiyada bo'lsa, hodisa kelmaydi — darhol success.

event.oldVersion — eski versiya (yangi bazada 0), event.newVersion — so'ralgan versiya. createIndex("turkum", "turkum") — birinchisi indeks nomi, ikkinchisi — obyektdagi qaysi maydon.

4. Yozish: tranzaksiya ichida

4.1 put va tranzaksiya tugashi

js
function tugashiniKut(tx) {
  return new Promise((resolve, reject) => {
    tx.oncomplete = () => resolve();
    tx.onerror = () => reject(tx.error);
    tx.onabort = () => reject(tx.error);
  });
}

const MENYU = [
  { id: 1, nom: "Osh", narx: 35000, turkum: "taom" },
  { id: 2, nom: "Lag'mon", narx: 28000, turkum: "taom" },
  { id: 3, nom: "Manti", narx: 30000, turkum: "taom" },
  { id: 4, nom: "Ko'k choy", narx: 5000, turkum: "ichimlik" },
];

const tx = db.transaction("taomlar", "readwrite");
const ombor = tx.objectStore("taomlar");
for (const taom of MENYU) {
  ombor.put(taom);
}
await tugashiniKut(tx);
console.log("Menyu saqlandi");

Konsolda:

text
Menyu saqlandi

Qadamlar:

  1. db.transaction("taomlar", "readwrite") — taomlar omboriga yozish huquqi bilan tranzaksiya. Faqat o'qish kerak bo'lsa — ikkinchi argumentsiz ("readonly" — standart).
  2. tx.objectStore("taomlar") — tranzaksiya ichidagi ombor.
  3. put(taom) — yozuvni qo'shadi, shu kalit bor bo'lsa — almashtiradi. To'rtta put ni kutmasdan ketma-ket chaqirdik: ular navbatga turadi va tartib bilan bajariladi.
  4. tx.oncomplete — hamma amal muvaffaqiyatli tugadi va diskka yozildi. "Saqlandi" deyish uchun aynan shu hodisani kutamiz, alohida put larni emas.

localStorage dan farqiga qarang: JSON.stringify yo'q. Obyekt o'zicha saqlanadi.

4.2 add va put farqi

add faqat yangi kalit qabul qiladi. Shu kalit bor bo'lsa — xato:

js
const tx2 = db.transaction("taomlar", "readwrite");
try {
  const ombor2 = tx2.objectStore("taomlar");
  await sorovniKut(ombor2.add({ id: 1, nom: "Osh" }));
} catch (xato) {
  console.log(xato.name + ": " + xato.message);
}

Konsolda:

text
ConstraintError: Key already exists in the object store.

Tarjimasi: "Cheklov xatosi: bu kalit omborda allaqachon bor." Qoida: "ustidan yozsa ham bo'ladi" — put, "takrorlanmasin, takrorlansa — xato" — add.

5. O'qish: kalit, indeks va oraliq

5.1 get, count, getAll

js
const oqish = db.transaction("taomlar").objectStore("taomlar");

const lagmon = await sorovniKut(oqish.get(2));
console.log(lagmon.nom, lagmon.narx);
console.log(await sorovniKut(oqish.get(99)));
console.log(await sorovniKut(oqish.count()));

const turkum = oqish.index("turkum");
const ichimliklar = await sorovniKut(turkum.getAll("ichimlik"));
console.log(ichimliklar.map((t) => t.nom).join(", "));

Konsolda:

text
Lag'mon 28000
undefined
4
Ko'k choy
  • get(kalit) — bitta yozuv; topilmasa — undefined (localStorage dagi null emas!).
  • count() — yozuvlar soni.
  • index("turkum").getAll("ichimlik") — turkum maydoni "ichimlik" bo'lgan hamma yozuv. Ombordagi hamma yozuvni aylanib chiqmasdan — indeks to'g'ridan-to'g'ri ko'rsatadi.

Bitta tranzaksiyada uch marta await qildik va u yopilib qolmadi. Sabab: har await dan keyingi kod so'rovning success hodisasi bilan bir xil "navbatda" ishlaydi — microtask'da (Event loop). Bu muhim, chunki keyingi bo'limdagi tuzoq aynan shu chegarada.

5.2 Oraliq va kursor

Kalit yoki indeks bo'yicha oraliq bilan ham o'qish mumkin. IDBKeyRange buni ifodalaydi:

  • IDBKeyRange.bound(2, 3) — 2 dan 3 gacha (ikkalasi ham kiradi);
  • IDBKeyRange.upperBound(30000) — 30 000 gacha;
  • IDBKeyRange.lowerBound(10000) — 10 000 dan boshlab.

Yozuvlarni bittalab aylanish kerak bo'lsa (masalan, millionta yozuvni xotiraga birdaniga yuklamaslik uchun) — kursor:

js
const oraliqli = db.transaction("taomlar").objectStore("taomlar");
const ikkiUchOraliq = IDBKeyRange.bound(2, 3);
const ikkiUch = await sorovniKut(oraliqli.getAll(ikkiUchOraliq));
console.log(ikkiUch.map((t) => t.nom).join(", "));

const kursor = oraliqli.openCursor();
await new Promise((resolve) => {
  kursor.onsuccess = () => {
    const k = kursor.result;
    if (k === null) {
      resolve();
      return;
    }
    console.log(k.key, k.value.nom);
    k.continue();
  };
});

Konsolda:

text
Lag'mon, Manti
1 Osh
2 Lag'mon
3 Manti
4 Ko'k choy

Kursor bitta success hodisasini bir necha marta chiqaradi: har yozuv uchun bir marta, oxirida esa result — null. k.continue() — "keyingisiga o't". Shuning uchun uni bitta Promise bilan o'rab bo'lmaydi — biz oxirini (null) kutdik. Bu noqulaylikni idb kutubxonasi hal qiladi.

5.3 O'chirish

  • ombor.delete(kalit) — bitta yozuv;
  • ombor.clear() — ombordagi hammasi;
  • indexedDB.deleteDatabase("bahor") — butun baza.

Uchalasi ham so'rov qaytaradi va readwrite tranzaksiyada (oxirgisi — tranzaksiyasiz) ishlaydi.

Tekshirib ko'ring: get(5) va localStorage.getItem("5") — yozuv yo'q bo'lsa nima qaytaradi?

Javob

get(5) — undefined, getItem("5") — null. Ikki API ikki xil kelishuvda yozilgan. Tekshirishda shuni eslang: IndexedDB'da === undefined (yoki shunchaki if (!yozuv), chunki yozuv — obyekt).

6. Nimani saqlash mumkin va tranzaksiya tuzog'i

6.1 Structured clone

IndexedDB obyektni JSON qilmaydi — u structured clone ("tuzilmali nusxa") algoritmi bilan nusxalaydi. Bu algoritmni structuredClone dan bilasiz. Shuning uchun Date, Map, Set, Blob (fayl, rasm) to'g'ridan-to'g'ri saqlanadi. Lekin funksiya va klass "shaxsiyati" saqlanmaydi:

js
class Taom {
  constructor(id) {
    this.id = id;
  }
  narxi() {
    return 1;
  }
}
const tx4 = db.transaction("taomlar", "readwrite");
const ombor4 = tx4.objectStore("taomlar");
await sorovniKut(ombor4.put({
  id: 6,
  nom: "Non",
  sana: new Date(2026, 9, 5),
  teglar: new Set(["issiq"]),
}));
const non = await sorovniKut(ombor4.get(6));
console.log(non.sana instanceof Date, non.teglar.has("issiq"));

await sorovniKut(ombor4.put(new Taom(8)));
const t8 = await sorovniKut(ombor4.get(8));
console.log(t8 instanceof Taom, typeof t8.narxi);

try {
  ombor4.put({ id: 7, hisobla: () => 1 });
} catch (xato) {
  console.log(xato.name + ": " + xato.message);
}

// sinov yozuvlarini tozalaymiz
await sorovniKut(ombor4.delete(6));
await sorovniKut(ombor4.delete(8));

Konsolda:

text
true true
false undefined
DataCloneError: Failed to execute 'put' on 'IDBObjectStore': () => 1 could not be cloned.
  • Date — Date bo'lib, Set — Set bo'lib qaytdi. JSON'da ikkalasi buzilardi.
  • Taom nusxasi oddiy obyekt bo'lib qaytdi: maydonlar bor, metodlar va instanceof — yo'q. vazifalar dagi Vazifa.dan kabi "qayta yasash" baribir kerak.
  • Funksiyani nusxalab bo'lmaydi — DataCloneError ("nusxalash xatosi"): "() => 1 nusxalanmadi."

6.2 Tranzaksiya o'zi yopiladi

Tranzaksiyada bajarilmagan so'rov qolmasa va joriy task tugasa, u avtomatik yakunlanadi (auto-commit). Shuning uchun tranzaksiya ichida IndexedDB'ga tegishli bo'lmagan narsani kutish — xato:

js
const tx3 = db.transaction("taomlar", "readwrite");
const ombor3 = tx3.objectStore("taomlar");
const javob = await fetch("https://ilmhamroh.uz/api/mashq/menyu");
ombor3.put({ id: 5, nom: "Somsa", narx: 8000, turkum: "taom" });
text
TransactionInactiveError: Failed to execute 'put' on 'IDBObjectStore': The transaction has finished.

Tarjimasi: "Tranzaksiya faol emas: put bajarilmadi — tranzaksiya tugagan." fetch javobi alohida task sifatida keladi (Event loop). Unga qadar tranzaksiyada kutilayotgan so'rov yo'q edi — brauzer uni yopib qo'ydi. Vaqt bo'yicha qarang:

flowchart TD
  A["tx ochildi"] --> B["await fetch — kod kutib turibdi"]
  B --> C["tx da kutilayotgan so'rov yo'q, task tugadi"]
  C --> D["brauzer tx ni yopdi"]
  D --> E["fetch javobi keldi — yangi task"]
  E --> F["put → TransactionInactiveError"]

Do'kon kassasiga o'xshaydi: kassir navbatda hech kim yo'qligini ko'rib, kassani yopdi. Siz pulni olib kelguncha kassa yopiq.

Tuzatish: avval tarmoqdan oling, keyin tranzaksiya oching va faqat IndexedDB amallarini qiling:

js
const javob = await fetch("https://ilmhamroh.uz/api/mashq/menyu");
const menyu = await javob.json();
const tx5 = db.transaction("taomlar", "readwrite");
for (const taom of menyu) {
  tx5.objectStore("taomlar").put(taom);
}
await tugashiniKut(tx5);
console.log("Serverdagi menyu keshlandi:", menyu.length);

Konsolda:

text
Serverdagi menyu keshlandi: 4

/api/mashq/menyu — kursning «Bahor» mashq API'si (fetch asoslari); javobi — {id, nom, narx, turkum} shaklidagi to'rtta taom. setTimeout bilan kutish ham xuddi shu xatoni beradi — u ham alohida task.

Tekshirib ko'ring: Sardor bitta readwrite tranzaksiyada 100 ta put chaqirdi, 57-chisi ConstraintError berdi (u add bo'lgan ekan). Bazada nechta yangi yozuv qoladi?

Javob

Nolta. Ushlanmagan xato tranzaksiyani bekor qiladi (abort) va undagi hamma o'zgarish — oldingi 56 tasi ham — orqaga qaytariladi. Bu tranzaksiyaning "hammasi yoki hech biri" va'dasi.

7. Versiya migratsiyasi

7.1 Yangi indeks va yangi ombor

Ilova o'sadi: «Bahor» narx bo'yicha filtr va bronlar omborini xohladi. Omborni faqat upgradeneeded ichida o'zgartirish mumkin — demak versiyani 2 ga oshiramiz va eski versiyadan kelgan foydalanuvchini ham, butunlay yangisini ham to'g'ri ko'taramiz:

js
function bazaniOch2() {
  const sorov = indexedDB.open("bahor", 2);
  sorov.onupgradeneeded = (event) => {
    const db = sorov.result;
    const tx = sorov.transaction;
    const { oldVersion, newVersion } = event;
    console.log(`Yangilash: ${oldVersion} → ${newVersion}`);
    if (event.oldVersion < 1) {
      const taomlar = db.createObjectStore("taomlar", {
        keyPath: "id",
      });
      taomlar.createIndex("turkum", "turkum");
    }
    if (event.oldVersion < 2) {
      tx.objectStore("taomlar").createIndex("narx", "narx");
      db.createObjectStore("bronlar", { autoIncrement: true });
    }
  };
  sorov.onblocked = () => {
    console.log("Kutilmoqda: boshqa tab eski versiyani yopmagan");
  };
  return sorovniKut(sorov);
}

Usul — "zinapoya": har if bitta versiyaning o'zgarishi. 0-versiyadan kelgan foydalanuvchi ikkala zinadan o'tadi, 1-versiyadagisi — faqat ikkinchisidan. Eski if lar o'chirilmaydi: yarim yildan beri saytga kirmagan foydalanuvchi ham bor.

sorov.transaction — upgradeneeded ning maxsus tranzaksiyasi. Mavjud omborga indeks qo'shish uchun omborni shu orqali olamiz.

Natijani tekshiramiz. Birinchi to'rt qator — 1-versiyali db ulanishiga "yangi versiya keldi, yopil" deyish (nega — keyingi bo'limda):

js
db.onversionchange = () => {
  db.close();
  console.log("Eski ulanish yopildi");
};
const db2 = await bazaniOch2();
const taomOmbori = db2.transaction("taomlar").objectStore("taomlar");
console.log([...taomOmbori.indexNames].join(", "));
const narx = taomOmbori.index("narx");
const gacha30 = IDBKeyRange.upperBound(30000);
const arzon = await sorovniKut(narx.getAll(gacha30));
console.log(arzon.map((t) => t.nom).join(", "));

const txBron = db2.transaction("bronlar", "readwrite");
const bronlar = txBron.objectStore("bronlar");
const bron = { ism: "Dilshod aka", soni: 4 };
const kalit = await sorovniKut(bronlar.add(bron));
console.log("bron kaliti:", kalit);

Konsolda:

text
Eski ulanish yopildi
Yangilash: 1 → 2
narx, turkum
Ko'k choy, Lag'mon, Manti
bron kaliti: 1

Narx indeksi yozuvlarni narx tartibida berdi: 5 000, 28 000, 30 000. bronlar da autoIncrement — kalitni brauzer o'zi berdi: 1, keyingisi 2.

7.2 Ikki tab va blocked

Foydalanuvchida sayt ikki tabda ochiq. Birinchisida eski kod — 1-versiyali baza ochiq turibdi. Ikkinchi tabda yangi kod 2-versiyani so'radi. Yangilash eski ulanish yopilmaguncha boshlanmaydi: ikkinchi tabda blocked hodisasi keladi. Chrome 154 da ikki tab bilan sinaganimizda aynan shunday bo'ldi: avval "Kutilmoqda", birinchi tabda db.close() dan keyingina Yangilash: 1 → 2.

Eski ulanish buni o'zi sezishi uchun versionchange hodisasi bor — yuqoridagi db.onversionchange aynan shu. Yaxshi kod uni doim tinglaydi: bazani yopadi va "Ilova yangilandi — sahifani yangilang" deb aytadi. Konsoldagi eski db ochiq tursa, bitta tabda ham bazaniOch2() abadiy kutib qolardi.

8. idb kutubxonasi

Xom API'ni har loyihada o'rab chiqish — takror ish. idb — Jake Archibald yozgan kichik (taxminan 1 KB) kutubxona: u xuddi shu IndexedDB'ni Promise bilan qaytaradi. Joriy versiya — 8.0.3. Brauzerda CDN'dan to'g'ridan-to'g'ri import qilsa bo'ladi (Brauzerda ESM va import maps):

js
import { openDB } from "https://cdn.jsdelivr.net/npm/idb@8.0.3/+esm";

const baza = await openDB("bahor-idb", 1, {
  upgrade(db) {
    const taomlar = db.createObjectStore("taomlar", {
      keyPath: "id",
    });
    taomlar.createIndex("turkum", "turkum");
  },
});

await baza.put("taomlar", {
  id: 1, nom: "Osh", narx: 35000, turkum: "taom",
});
await baza.put("taomlar", {
  id: 4, nom: "Ko'k choy", narx: 5000, turkum: "ichimlik",
});
const osh = await baza.get("taomlar", 1);
console.log(osh.nom, osh.narx);
const ichimlik = await baza.getAllFromIndex(
  "taomlar", "turkum", "ichimlik",
);
console.log(ichimlik.map((t) => t.nom).join(", "));
console.log(await baza.count("taomlar"));

Konsolda:

text
Osh 35000
Ko'k choy
2

Bu kod <script type="module"> ichida ishlaydi (DevTools konsoli import ni qabul qilmaydi — await import("https://...") dan foydalaning). Farq ko'rinib turibdi: sorovniKut, tugashiniKut yo'q. baza.put("taomlar", ...) — o'zi tranzaksiya ochadi va tugashini kutadi. Bir necha amal bitta tranzaksiyada kerak bo'lsa — baza.transaction(...) va oxirida await tx.done.

Kutubxona tranzaksiya tuzog'ini yo'q qilmaydi: idb bilan ham tranzaksiya ichida fetch ni kutish — o'sha TransactionInactiveError. Uni ishlatish uchun xom API'ni tushunish baribir shart — shuning uchun avval uni o'rgandik. Loyihada npm i idb bilan o'rnatiladi; npm — 16-qismda, hozir bilish shart emas.

9. Xotira kvotasi va OPFS

9.1 Qancha joy bor: navigator.storage.estimate()

IndexedDB, Cache API (sahifa fayllari uchun kesh — 13-qismda) va OPFS (shu bo'lim oxirida) bitta umumiy "xotira" dan joy oladi. Qancha ishlatilgani va qancha ruxsat borligini so'rash mumkin:

js
const { usage, quota } = await navigator.storage.estimate();
console.log(Math.round(usage / 1024), "KB ishlatildi");
console.log(Math.round(quota / 1024 ** 3), "GB kvota");

Bizning sinovda (Chrome 154, headless) bir necha KB ishlatilgan va 10 GB kvota chiqdi. Sizda boshqacha bo'ladi: Chrome kvotani diskdagi joyga qarab beradi — bitta origin uchun umumiy disk hajmining 60% gacha. Raqamlar taxminiy — xavfsizlik uchun brauzer aniq hajmni biroz "yashiradi".

Kvota tugasa, yozish QuotaExceededError bilan rad etiladi — tranzaksiyaning error ida. Katta yozuvdan oldin estimate() bilan tekshirish — yaxshi odat.

9.2 "Eng yaxshi urinish" va persist()

Standart bo'yicha sayt ma'lumoti best-effort ("imkon qadar") rejimida. Disk to'lib qolsa, brauzer eng uzoq ishlatilmagan saytlarning ma'lumotini so'ramasdan o'chiradi. Safari bundan ham qattiqroq: saytni 7 kun ochmasangiz, uning skript yozgan ma'lumotlarini o'chirishi mumkin (bosh ekranga o'rnatilgan veb-ilovalar bundan mustasno).

Muhim ma'lumot uchun doimiy (persistent) saqlashni so'rash mumkin:

js
console.log(await navigator.storage.persisted());
const berildi = await navigator.storage.persist();
console.log("Doimiy saqlash:", berildi);

persisted() — hozirgi holat. persist() — so'rov: Firefox foydalanuvchidan so'raydi, Chrome esa o'zi hal qiladi (sayt xatcho'pda bo'lsa, ilova sifatida o'rnatilgan bo'lsa yoki ko'p ishlatilsa — rozi). Doimiy ma'lumotni brauzer faqat foydalanuvchi o'zi tozalasa o'chiradi.

Qoida o'zgarmaydi: brauzerdagi ma'lumot — nusxa. Yagona nusxa bo'lsa, u bir kun yo'qoladi. Muhim narsa serverda ham saqlansin.

9.3 OPFS: sayt uchun shaxsiy fayl tizimi

OPFS (Origin Private File System) — har origin uchun brauzer ichidagi yashirin papka. U foydalanuvchining haqiqiy fayllariga ko'rinmaydi, lekin sayt unda fayl yaratib, o'qiy oladi. Katta binar ma'lumot (video, SQLite bazasi faylini) bilan tez ishlash uchun:

js
const ildiz = await navigator.storage.getDirectory();
const fayl = await ildiz.getFileHandle("eslatma.txt", {
  create: true,
});
const yozuvchi = await fayl.createWritable();
await yozuvchi.write("Osh — 35 000 so'm");
await yozuvchi.close();

const matn = await (await fayl.getFile()).text();
console.log(matn);

Konsolda:

text
Osh — 35 000 so'm

OPFS 2023-yildan beri hamma asosiy brauzerlarda (web-features: Baseline, keng tarqalgan — 2025-09). Oddiy ilovalarga IndexedDB yetadi; OPFS — rasm tahrirlovchi yoki brauzerda ishlaydigan SQLite kabi og'ir vositalar uchun. Fayllar bilan ishlash (File, Blob) — 13-qismda (File va Blob).

10. Qaysi omborni tanlash

Ma'lumot Ombor Nega
Tema, til, kichik sozlama localStorage Sodda, sinxron, kichik
Bitta tashrif uchun qoralama sessionStorage Tab yopilsa o'chadi
Server bilishi kerak (sessiya) HttpOnly cookie Har so'rov bilan ketadi
Ko'p yozuv, qidiruv, oflayn IndexedDB Katta, asinxron, indeks
Katta binar fayl OPFS (yoki IndexedDB'da Blob) Tez fayl o'qish/yozish

Oflayn ishlaydigan to'liq ilova uchun IndexedDB yoniga Service Worker va Cache API qo'shiladi — ular sahifaning o'zini ham internetsiz ochadi. Bu — 13-qismda (Kesh strategiyalari va offline ishlash).

11. Ko'p uchraydigan xatolar

11.1 Ombor yaratishni upgradeneeded dan tashqarida qilish

db.createObjectStore(...) ni success dan keyin chaqirish — InvalidStateError. Tuzatish: versiyani oshiring va o'zgarishni upgradeneeded ichiga yozing.

11.2 Versiyani oshirmasdan yangi ombor kutish

Kodga yangi ombor qo'shildi, lekin open("bahor", 1) qoldi. Eski foydalanuvchida upgradeneeded kelmaydi va transaction("bronlar") — NotFoundError. Tuzatish: sxema o'zgarsa — versiya ham o'zgaradi.

11.3 put larni "saqlandi" deb hisoblash

put so'rovi muvaffaqiyatli bo'ldi, lekin tranzaksiya keyinroq bekor bo'lishi mumkin. Tuzatish: "saqlandi" xabarini tx.oncomplete (yoki idb da await tx.done) dan keyin bering.

11.4 Klass nusxasini o'zicha qaytadi deb kutish

O'qilgan yozuv — oddiy obyekt. Tuzatish: Vazifa.dan(yozuv) kabi fabrika metodi bilan qayta yasang.

12. Mashqlar

1-mashq (oson): Bronlar ro'yxati

Yuqoridagi 2-versiyali bahor bazasidagi bronlar omboriga uchta bron qo'shing ({ism, soni, vaqt}) — bitta tranzaksiyada. Keyin hammasini o'qib, Dilshod aka — 4 kishi, 19:00 ko'rinishida chiqaring. Ishora: getAll() tartibi — kalit tartibi, autoIncrement kalitlari qo'shilish tartibida.

Yechim
js
const BRONLAR = [
  { ism: "Dilshod aka", soni: 4, vaqt: "19:00" },
  { ism: "Aziz", soni: 2, vaqt: "13:30" },
  { ism: "Jasur", soni: 6, vaqt: "20:00" },
];

const txB = db2.transaction("bronlar", "readwrite");
for (const bron of BRONLAR) {
  txB.objectStore("bronlar").add(bron);
}
await tugashiniKut(txB);

const hammasi = await sorovniKut(
  db2.transaction("bronlar").objectStore("bronlar").getAll(),
);
for (const b of hammasi) {
  console.log(`${b.ism} — ${b.soni} kishi, ${b.vaqt}`);
}

Konsolda (bo'sh bronlar omborida):

text
Dilshod aka — 4 kishi, 19:00
Aziz — 2 kishi, 13:30
Jasur — 6 kishi, 20:00

Darsdagi bloklarni ketma-ket bajargan bo'lsangiz, boshida yana bitta qator chiqadi: Dilshod aka — 4 kishi, undefined. Bu «Versiya migratsiyasi» bo'limidagi sinov broni, unda vaqt yo'q edi. IndexedDB yozuv shaklini tekshirmaydi — buni ilova kodi kafolatlaydi.

2-mashq (o'rta): Narx oralig'i

taomlarniTop(min, max) funksiyasini yozing: narx indeksi bo'yicha min va max oralig'idagi taomlar nomini massiv qilib qaytarsin. taomlarniTop(10000, 30000) — ["Lag'mon", "Manti"]. Ishora: IDBKeyRange.bound.

Yechim
js
async function taomlarniTop(min, max) {
  const indeks = db2.transaction("taomlar")
    .objectStore("taomlar")
    .index("narx");
  const taomlar = await sorovniKut(
    indeks.getAll(IDBKeyRange.bound(min, max)),
  );
  return taomlar.map((t) => t.nom);
}

console.log((await taomlarniTop(10000, 30000)).join(", "));
console.log((await taomlarniTop(0, 1000)).length);

Konsolda:

text
Lag'mon, Manti
0

Natija narx tartibida — indeks shunday saqlaydi. Mos taom bo'lmasa — bo'sh massiv, xato emas.

3-mashq (qiyin): Oflayn menyu keshi

menyuniOl() funksiyasini yozing:

  1. Serverdan https://ilmhamroh.uz/api/mashq/menyu ni so'rasin; muvaffaqiyatli bo'lsa — taomlar omboriga yozsin va {manba: "server", taomlar} qaytarsin.
  2. Tarmoq xatosi bo'lsa yoki response.ok false bo'lsa — IndexedDB'dagi nusxani {manba: "kesh", taomlar} qilib qaytarsin.

Sinash: DevTools → Network → Offline ni yoqib, qayta chaqiring. Ishora: tranzaksiyani fetch dan keyin oching; fetch tarmoq yo'qligida TypeError bilan rad etiladi (fetch asoslari).

Yechim
js
async function menyuniOl() {
  try {
    const javob = await fetch("https://ilmhamroh.uz/api/mashq/menyu");
    if (!javob.ok) {
      throw new Error(`HTTP ${javob.status}`);
    }
    const menyu = await javob.json();
    const tx = db2.transaction("taomlar", "readwrite");
    for (const taom of menyu) {
      tx.objectStore("taomlar").put(taom);
    }
    await tugashiniKut(tx);
    return { manba: "server", taomlar: menyu };
  } catch (xato) {
    console.warn("Tarmoq yo'q, keshdan olinadi:", xato.message);
    const ombor = db2.transaction("taomlar").objectStore("taomlar");
    const keshda = await sorovniKut(ombor.getAll());
    return { manba: "kesh", taomlar: keshda };
  }
}

const { manba, taomlar } = await menyuniOl();
console.log(manba, taomlar.length);

Konsolda (internet bor):

text
server 4

Offline rejimda konsolda avval ogohlantirish (Tarmoq yo'q, keshdan olinadi: Failed to fetch), keyin kesh 4. Ikkala holat ham Chrome 154 da avtomatik sinovning oflayn rejimi bilan tekshirildi.

Bu — "avval tarmoq, bo'lmasa kesh" (network first) strategiyasi. Teskarisi — "avval kesh, keyin tarmoqdan yangilash" — ekranni tezroq ko'rsatadi; ikkalasini Kesh strategiyalari darsida taqqoslaymiz.

Kodni kurs/mashqlar/11/28-indexeddb/index.html (<script type="module"> ichida, sorovniKut, tugashiniKut va bazaniOch2 bilan birga) saqlang:

bash
git add 11/28-indexeddb/index.html
git commit -m "11/28: IndexedDB bilan oflayn menyu keshi"

vazifalar da bu darsga qadam yo'q: uning ro'yxati kichik va localStorage yetadi. IndexedDB'ga o'tish ma'lumot megabaytlarga yetganda yoki fayl (rasm) saqlash kerak bo'lganda o'rinli.

13. Real ishda

  • Oflayn ilovalar. Google Docs, Figma, Notion — ma'lumotni IndexedDB'da ushlaydi va internet qaytganda serverga sinxronlaydi.
  • Kutubxonalar. idb (yupqa qatlam), Dexie.js (so'rov tili bilan), localForage (localStorage ga o'xshash API, ichida IndexedDB).
  • PWA. Telefonga o'rnatiladigan veb-ilovalar — Service Worker + Cache API + IndexedDB uchligi (PWA, 13-qismda).
  • Intervyu. "localStorage va IndexedDB farqi?", "Tranzaksiya nega o'zi yopiladi?", "Brauzer ma'lumotni o'chirishi mumkinmi?" — ayniqsa oflayn ilovalar bilan ishlaydigan jamoalarda.

Xulosa

  • IndexedDB — brauzerdagi asinxron baza: baza → object store → yozuvlar (kalit bilan) + indekslar.
  • Har amal tranzaksiyada; "saqlandi" — oncomplete dan keyin. Tranzaksiya ichida tarmoq yoki taymerni kutmang.
  • Obyektlar structured clone bilan saqlanadi: Date, Map, Blob — ha; funksiya va klass metodlari — yo'q.
  • Sxema faqat upgradeneeded da o'zgaradi: versiyani oshiring, oldVersion bo'yicha "zinapoya" yozing.
  • Xom API o'rniga ko'pincha idb (8.0.3); joy — navigator.storage.estimate(), muhim ma'lumot — persist() va baribir serverda.

Keyingi dars: URL va URLSearchParams — manzilni matn sifatida emas, obyekt sifatida o'qish va o'zgartirish: qismlar, so'rov parametrlari va kodlash.

Manbalar

  • W3C: "Indexed Database API 3.0" — w3c.github.io/IndexedDB
  • MDN: "Using IndexedDB", "IDBKeyRange", "Storage quotas and eviction criteria", "Origin private file system" — developer.mozilla.org
  • Jake Archibald, idb — github.com/jakearchibald/idb
  • web.dev: "Storage for the web", "Persistent storage" — web.dev
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
IndexedDB: brauzerdagi ma'lumotlar bazasi va xotira kvotasi — IlmHamroh