如何在已有的IndexedDB数据库中新增对象仓库?
我想实现一个基于IndexedDB、类似localStorage的简易键值存储工具,定义了LocalStorageAsync类,结构如下:
class LocalStorageAsync { constructor(storeName?: string) getItem(key: string): Promise<string> setItem(key: string, value: string): Promise<void> }
实例化时,若未指定storeName则使用默认名称创建数据库,初始实现如下:
class LocalStorageAsync { //... constructor(storeName = 'default') { const openRequest = indexedDB.open('LocalStorageAsync') openRequest.onupgradeneeded = () => { const db = oRequest.result db.createObjectStore(storeKey) } this.#database = new Promise(resolve => openRequest.onsuccess = () => resolve(oRequest.result) } //... }
我希望通过storeName对数据库进行分区,即当数据库中不存在对应store时自动创建:
new LocalStorageAsync() // 会创建`LocalStorageAsync.default` new LocalStorageAsync('foo') // 会创建`LocalStorageAsync.foo`
最初尝试在onsuccess回调中实现,但发现db.createObjectStore()只能在onupgradeneeded回调中调用,该回调仅在数据库版本升级或首次创建时触发。我不在意数据库版本,但需要触发onupgradeneeded来新增store。请问能否从onsuccess回调触发onupgradeneeded?比如直接升级数据库版本:
openRequest.onsuccess = () => { if (!db.objectStoreNames.contains(storeKey)) { // 非真实方法 db.bumpVersion(db.version + 1) } }
或者直接创建名称为LocalStorageAsync.${storeName}的全新数据库会更好?
方案一:通过升级数据库版本触发onupgradeneeded创建新store
可以实现这个思路,但要遵循正确的版本升级流程:
- 在
onsuccess回调中检查目标store是否存在,若不存在则关闭当前数据库连接,以当前版本+1的版本号重新打开数据库 - 在新的
onupgradeneeded回调中创建缺失的store
示例代码:
class LocalStorageAsync { #database; #storeName; constructor(storeName = 'default') { this.#storeName = storeName; this.#database = new Promise((resolve, reject) => { const openRequest = indexedDB.open('LocalStorageAsync'); openRequest.onsuccess = () => { const db = openRequest.result; if (db.objectStoreNames.contains(this.#storeName)) { resolve(db); return; } // 关闭当前连接,准备升级版本 db.close(); const upgradeRequest = indexedDB.open('LocalStorageAsync', db.version + 1); upgradeRequest.onupgradeneeded = () => { const upgradeDb = upgradeRequest.result; if (!upgradeDb.objectStoreNames.contains(this.#storeName)) { upgradeDb.createObjectStore(this.#storeName); } }; upgradeRequest.onsuccess = () => resolve(upgradeRequest.result); upgradeRequest.onerror = () => reject(upgradeRequest.error); }; openRequest.onupgradeneeded = () => { const db = openRequest.result; if (!db.objectStoreNames.contains(this.#storeName)) { db.createObjectStore(this.#storeName); } }; openRequest.onerror = () => reject(openRequest.error); }); } async getItem(key) { const db = await this.#database; const transaction = db.transaction(this.#storeName); const store = transaction.objectStore(this.#storeName); const request = store.get(key); return new Promise((resolve) => { request.onsuccess = () => resolve(request.result); }); } async setItem(key, value) { const db = await this.#database; const transaction = db.transaction(this.#storeName, 'readwrite'); const store = transaction.objectStore(this.#storeName); store.put(value, key); return new Promise((resolve, reject) => { transaction.oncomplete = () => resolve(); transaction.onerror = () => reject(transaction.error); }); } }
这个方案的优势是所有store集中在同一个数据库,便于统一管理;缺点是每次新增store都要执行关闭连接、升级版本、重新打开的流程,会带来额外的操作开销。
方案二:每个store对应单独的数据库
直接将数据库名称设为LocalStorageAsync.${storeName},每个实例对应独立的数据库:
class LocalStorageAsync { #database; #dbName; constructor(storeName = 'default') { this.#dbName = `LocalStorageAsync.${storeName}`; this.#database = new Promise((resolve, reject) => { const openRequest = indexedDB.open(this.#dbName); openRequest.onupgradeneeded = () => { const db = openRequest.result; // 每个数据库只需要一个默认store db.createObjectStore('keyval'); }; openRequest.onsuccess = () => resolve(openRequest.result); openRequest.onerror = () => reject(openRequest.error); }); } async getItem(key) { const db = await this.#database; const transaction = db.transaction('keyval'); const store = transaction.objectStore('keyval'); const request = store.get(key); return new Promise((resolve) => { request.onsuccess = () => resolve(request.result); }); } async setItem(key, value) { const db = await this.#database; const transaction = db.transaction('keyval', 'readwrite'); const store = transaction.objectStore('keyval'); store.put(value, key); return new Promise((resolve, reject) => { transaction.oncomplete = () => resolve(); transaction.onerror = () => reject(transaction.error); }); } }
这个方案的优势是逻辑简单,无需处理版本升级,每个实例的数据库完全独立,避免跨store的干扰;缺点是数据库数量会随storeName增多而增加,但IndexedDB对数据库数量的限制足够日常使用,一般不会出现问题。
方案选择
如果希望所有数据集中管理,选择方案一;如果追求实现简单、低维护成本,方案二更合适,也是同类工具的常见实现方式。
内容的提问来源于stack exchange,提问作者David Alsh

