Mundarija (38)
- Bu darsda
- 1. Nega bu kerak?
- 2. IndexedDB tuzilishi
- 2.1 To'rtta asosiy tushuncha
- 2.2 Asinxron, lekin Promise emas
- 3. Bazani ochish va omborlar yaratish
- 3.1 indexedDB.open va upgradeneeded
- 4. Yozish: tranzaksiya ichida
- 4.1 put va tranzaksiya tugashi
- 4.2 add va put farqi
- 5. O'qish: kalit, indeks va oraliq
- 5.1 get, count, getAll
- 5.2 Oraliq va kursor
- 5.3 O'chirish
- 6. Nimani saqlash mumkin va tranzaksiya tuzog'i
- 6.1 Structured clone
- 6.2 Tranzaksiya o'zi yopiladi
- 7. Versiya migratsiyasi
- 7.1 Yangi indeks va yangi ombor
- 7.2 Ikki tab va blocked
- 8. idb kutubxonasi
- 9. Xotira kvotasi va OPFS
- 9.1 Qancha joy bor: navigator.storage.estimate()
- 9.2 "Eng yaxshi urinish" va persist()
- 9.3 OPFS: sayt uchun shaxsiy fayl tizimi
- 10. Qaysi omborni tanlash
- 11. Ko'p uchraydigan xatolar
- 11.1 Ombor yaratishni upgradeneeded dan tashqarida qilish
- 11.2 Versiyani oshirmasdan yangi ombor kutish
- 11.3 put larni "saqlandi" deb hisoblash
- 11.4 Klass nusxasini o'zicha qaytadi deb kutish
- 12. Mashqlar
- 1-mashq (oson): Bronlar ro'yxati
- 2-mashq (o'rta): Narx oralig'i
- 3-mashq (qiyin): Oflayn menyu keshi
- 13. Real ishda
- Xulosa
- Manbalar
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)→upgradeneededda omborlar yaratiladi → har o'qish va yozish tranzaksiya ichida. Xom API hodisalarga qurilgan va noqulay, shuning uchun amalda u Promise'ga o'raladi yokiidbkutubxonasi ishlatiladi. Qancha joy borligininavigator.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,
awaitbilan ishlatasiz. - Versiya oshganda bazani buzmasdan yangilaysiz (migratsiya).
idbkutubxonasi,navigator.storage.estimate/persistva 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'shindex.html→ Live Server →F12→ Console. Bloklarni konsolga ketma-ket qo'ying: konsol eng yuqori darajadagiawaitni 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 —
localStoragedagi 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:
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
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):
Yangilash: 0 → 1
bahor 1 taomlarIkkinchi 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
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:
Menyu saqlandiQadamlar:
db.transaction("taomlar", "readwrite")—taomlaromboriga yozish huquqi bilan tranzaksiya. Faqat o'qish kerak bo'lsa — ikkinchi argumentsiz ("readonly"— standart).tx.objectStore("taomlar")— tranzaksiya ichidagi ombor.put(taom)— yozuvni qo'shadi, shu kalit bor bo'lsa — almashtiradi. To'rttaputni kutmasdan ketma-ket chaqirdik: ular navbatga turadi va tartib bilan bajariladi.tx.oncomplete— hamma amal muvaffaqiyatli tugadi va diskka yozildi. "Saqlandi" deyish uchun aynan shu hodisani kutamiz, alohidaputlarni 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:
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:
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
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:
Lag'mon 28000
undefined
4
Ko'k choyget(kalit)— bitta yozuv; topilmasa —undefined(localStoragedaginullemas!).count()— yozuvlar soni.index("turkum").getAll("ichimlik")—turkummaydoni"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:
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:
Lag'mon, Manti
1 Osh
2 Lag'mon
3 Manti
4 Ko'k choyKursor 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)valocalStorage.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:
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:
true true
false undefined
DataCloneError: Failed to execute 'put' on 'IDBObjectStore': () => 1 could not be cloned.Date—Datebo'lib,Set—Setbo'lib qaytdi. JSON'da ikkalasi buzilardi.Taomnusxasi oddiy obyekt bo'lib qaytdi: maydonlar bor, metodlar vainstanceof— yo'q.vazifalardagiVazifa.dankabi "qayta yasash" baribir kerak.- Funksiyani nusxalab bo'lmaydi —
DataCloneError("nusxalash xatosi"): "() => 1nusxalanmadi."
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:
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" });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:
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:
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
readwritetranzaksiyada 100 taputchaqirdi, 57-chisiConstraintErrorberdi (uaddbo'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:
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):
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:
Eski ulanish yopildi
Yangilash: 1 → 2
narx, turkum
Ko'k choy, Lag'mon, Manti
bron kaliti: 1Narx 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):
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:
Osh 35000
Ko'k choy
2Bu 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:
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:
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:
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:
Osh — 35 000 so'mOPFS 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
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):
Dilshod aka — 4 kishi, 19:00
Aziz — 2 kishi, 13:30
Jasur — 6 kishi, 20:00Darsdagi 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
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:
Lag'mon, Manti
0Natija narx tartibida — indeks shunday saqlaydi. Mos taom bo'lmasa — bo'sh massiv, xato emas.
3-mashq (qiyin): Oflayn menyu keshi
menyuniOl() funksiyasini yozing:
- Serverdan
https://ilmhamroh.uz/api/mashq/menyuni so'rasin; muvaffaqiyatli bo'lsa —taomlaromboriga yozsin va{manba: "server", taomlar}qaytarsin. - Tarmoq xatosi bo'lsa yoki
response.okfalsebo'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
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):
server 4Offline 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:
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 (localStoragega o'xshash API, ichida IndexedDB). - PWA. Telefonga o'rnatiladigan veb-ilovalar — Service Worker + Cache API + IndexedDB uchligi (PWA, 13-qismda).
- Intervyu. "
localStorageva 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" —
oncompletedan keyin. Tranzaksiya ichida tarmoq yoki taymerni kutmang. - Obyektlar structured clone bilan saqlanadi:
Date,Map,Blob— ha; funksiya va klass metodlari — yo'q. - Sxema faqat
upgradeneededda o'zgaradi: versiyani oshiring,oldVersionbo'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
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!