Brauzerda ma'lumot saqlash

IndexedDB

IndexedDB โ€” brauzer ichiga o'rnatilgan kuchli ma'lumotlar bazasi. U localStoragedan ancha kuchliroq: katta hajmli ma'lumot saqlaydi, murakkab so'rovlar va indekslarni qo'llab-quvvatlaydi, hamda deyarli har qanday turdagi qiymatni (obyektlar, fayllar, Bloblar) saqlay oladi. U tranzaksiyalar asosida ishlaydi va katta oflayn ilovalar uchun mo'ljallangan.

IndexedDB nima uchun kerak

localStorage oddiy, ammo cheklangan: faqat satr saqlaydi, ~5 MB bilan chegaralangan va sinxron. IndexedDB esa quyidagilarni beradi:

IndexedDB brauzerga xos va asinxron. Node.js/playground muhitida indexedDB ob'yekti mavjud emas โ€” quyidagi barcha misollar faqat brauzerda ishlaydi.

Bazani ochish va versiya

Ish indexedDB.open(nom, versiya) bilan boshlanadi. U bazani ochadi (yo'q bo'lsa yaratadi) va so'rov (request) obyektini qaytaradi:

// Brauzerda:
let openRequest = indexedDB.open('shop', 1);

openRequest.onsuccess = function() {
  let db = openRequest.result;
  console.log('Baza ochildi:', db.name);
};

openRequest.onerror = function() {
  console.error('Xato:', openRequest.error);
};

Ikkinchi argument โ€” versiya raqami (musbat butun son). Sxema (obyekt omborlari, indekslar) faqat versiya oshirilganda o'zgartiriladi. Versiya oshirilganda upgradeneeded hodisasi ishga tushadi โ€” sxemani aynan shu yerda quramiz:

let openRequest = indexedDB.open('shop', 1);

openRequest.onupgradeneeded = function() {
  let db = openRequest.result;
  // sxema o'zgarishlari shu yerda:
  if (!db.objectStoreNames.contains('books')) {
    db.createObjectStore('books', { keyPath: 'id' });
  }
};
Sxemani o'zgartirish (obyekt ombori yoki indeks yaratish) faqat upgradeneeded ichida mumkin. Oddiy onsuccess ichida buni qilib bo'lmaydi. Sxema o'zgarganda versiya raqamini oshirishingiz kerak.

Obyekt ombori (object store)

Obyekt ombori โ€” IndexedDB'da ma'lumot saqlanadigan asosiy tuzilma, xuddi SQL'dagi "jadval"ga o'xshaydi. U kalit bo'yicha qiymatlarni saqlaydi. Har bir omborda kalit qanday aniqlanishini belgilaymiz:

openRequest.onupgradeneeded = function() {
  let db = openRequest.result;

  // keyPath: obyektning maydoni kalit bo'ladi
  db.createObjectStore('books', { keyPath: 'id' });

  // yoki avtomatik oshib boruvchi kalit:
  // db.createObjectStore('logs', { autoIncrement: true });
};
Kalit โ€” noyob bo'lishi kerak. Bir xil kalitli obyektni put bilan saqlash mavjudini almashtiradi, add bilan esa xatolik beradi.

Tranzaksiya (transaction)

IndexedDB'da har qanday o'qish yoki yozish tranzaksiya ichida bajariladi. Tranzaksiya โ€” bu bir yoki bir nechta amalni atomar (butun holda muvaffaqiyatli yoki butun holda bekor) bajaruvchi guruh. db.transaction(omborlar, rejim) bilan yaratiladi:

// faqat o'qish uchun:
let tx = db.transaction('books', 'readonly');

// o'qish va yozish uchun:
let tx = db.transaction('books', 'readwrite');

// omborni olamiz:
let store = tx.objectStore('books');
// tranzaksiya tugashini kuzatish:
tx.oncomplete = function() {
  console.log('Tranzaksiya tugadi');
};
tx.onabort = function() {
  console.log('Tranzaksiya bekor qilindi:', tx.error);
};
readwriteni faqat kerak bo'lganda ishlating. Ortiqcha readwrite tranzaksiyalar boshqa yozishlarni bloklab, ilovani sekinlashtiradi.

Qo'shish va o'qish

Ombor obyektini olganimizdan so'ng, unga add/put bilan yozamiz va get bilan o'qiymiz. Har bir amal so'rov (request) qaytaradi:

let tx = db.transaction('books', 'readwrite');
let store = tx.objectStore('books');

let book = { id: 1, title: 'JavaScript', price: 100 };

let request = store.add(book);

request.onsuccess = function() {
  console.log('Qo'shildi, kalit:', request.result);
};
request.onerror = function() {
  console.log('Xato:', request.error);
};

O'qish kalit bo'yicha amalga oshiriladi:

let tx = db.transaction('books', 'readonly');
let store = tx.objectStore('books');

let request = store.get(1);

request.onsuccess = function() {
  let book = request.result;
  if (book) {
    console.log(book.title); // "JavaScript"
  } else {
    console.log('Topilmadi');
  }
};

Foydali metodlar:

Indekslar

Standart holatda IndexedDB'da faqat kalit bo'yicha qidirish mumkin. Boshqa maydon bo'yicha qidirish uchun indeks yaratamiz. Indeks โ€” bu ma'lum maydon bo'yicha saralangan qo'shimcha "jadval":

openRequest.onupgradeneeded = function() {
  let db = openRequest.result;
  let store = db.createObjectStore('books', { keyPath: 'id' });

  // 'price' maydoni bo'yicha indeks:
  store.createIndex('price_idx', 'price', { unique: false });
};

Endi indeks orqali narx bo'yicha qidirish mumkin:

let tx = db.transaction('books', 'readonly');
let store = tx.objectStore('books');
let index = store.index('price_idx');

// narxi aynan 100 bo'lganlarni olish:
let request = index.getAll(100);
request.onsuccess = function() {
  console.log(request.result); // narxi 100 bo'lgan kitoblar
};
Indekslar ham obyekt omborlari kabi faqat upgradeneeded ichida yaratiladi.

getAll va IDBKeyRange

Ko'p yozuvni bir vaqtda olish uchun getAll juda qulay. Oraliq bo'yicha filtrlash uchun IDBKeyRange ishlatiladi:

let index = store.index('price_idx');

// narxi 50 dan 150 gacha (chegaralar bilan):
let range = IDBKeyRange.bound(50, 150);
let request = index.getAll(range);

request.onsuccess = function() {
  console.log(request.result);
};

// boshqa oraliqlar:
// IDBKeyRange.lowerBound(100)  -> 100 va undan katta
// IDBKeyRange.upperBound(100)  -> 100 va undan kichik
// IDBKeyRange.only(100)        -> aynan 100

Promise o'ramchisi

IndexedDB'ning tabiiy API'si hodisalarga (onsuccess/onerror) asoslangan bo'lib, bu kodni murakkablashtiradi. Har bir so'rovni Promisega o'rab, async/await bilan ishlatish ancha qulay. Quyida oddiy o'ramchi:

function promisify(request) {
  return new Promise(function(resolve, reject) {
    request.onsuccess = function() {
      resolve(request.result);
    };
    request.onerror = function() {
      reject(request.error);
    };
  });
}

// ishlatish (async funksiya ichida):
async function getBook(db, id) {
  let tx = db.transaction('books', 'readonly');
  let store = tx.objectStore('books');
  let book = await promisify(store.get(id));
  return book;
}

Bazani ochishni ham Promise'ga o'rash mumkin:

function openDB(name, version, onUpgrade) {
  return new Promise(function(resolve, reject) {
    let request = indexedDB.open(name, version);
    request.onupgradeneeded = function() {
      onUpgrade(request.result);
    };
    request.onsuccess = function() {
      resolve(request.result);
    };
    request.onerror = function() {
      reject(request.error);
    };
  });
}
Amalda ko'p loyihalar Jake Archibald'ning idb kutubxonasini ishlatadi โ€” u aynan shunday Promise o'ramchisini tayyor holda beradi va IndexedDB bilan ishlashni sezilarli soddalashtiradi.

O'chirish va bazani tozalash

// bitta yozuvni o'chirish:
let tx = db.transaction('books', 'readwrite');
tx.objectStore('books').delete(1);

// omborni butunlay bo'shatish:
tx.objectStore('books').clear();

// butun bazani o'chirish:
let delReq = indexedDB.deleteDatabase('shop');
delReq.onsuccess = function() {
  console.log('Baza o'chirildi');
};

Qachon IndexedDB kerak

IndexedDB kuchli, lekin murakkab. Uni tanlashda quyidagilarni hisobga oling:

Aksincha, oddiy holatlar uchun soddaroq vositalar mos:

Katta va murakkab ma'lumot uchun IndexedDB tanlang; oddiy kalit-qiymat uchun localStorage yetarli va ancha sodda.

Xulosa