promise 封装 indexeddb 的核心是将 onsuccess/onerror 转为 resolve/reject,统一处理 opendb、读写删及游标遍历,提升可读性与错误可控性,推荐生产环境使用 idb 等成熟库。

用 Promise 包装 IndexedDB 的回调函数,核心是把 onsuccess 和 onerror 转成 resolve 和 reject,让原本嵌套、易出错的事件驱动逻辑变成可链式调用、可 await 的现代异步流程。关键不是重写整个 API,而是封装好打开数据库、读、写、删等常用操作。
封装 openDB:Promise 化 indexedDB.open
原生 indexedDB.open 返回一个 IDBOpenDBRequest 对象,靠监听事件响应。包装后返回 Promise,便于统一错误处理和版本升级控制:
function openDB(name, version = 1, upgradeCb) {
return new Promise((resolve, reject) => {
const req = indexedDB.open(name, version);
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
req.onupgradeneeded = (e) => {
const db = e.target.result;
if (typeof upgradeCb === 'function') {
upgradeCb(db, e.oldVersion, e.newVersion, e.transaction);
}
};
});
}
使用示例:
const db = await openDB('myApp', 2, (db, old, newV) => {
if (old
封装通用事务操作:read/write/delete 都走 Promise
IndexedDB 的增删改查必须在事务(transaction)中进行。为避免重复写 transaction.objectStore().get(...) 这类样板代码,可抽象一个 withTransaction 工具函数:
function withTransaction(db, storeName, mode = 'readonly', fn) {
return new Promise((resolve, reject) => {
const tx = db.transaction(storeName, mode);
const store = tx.objectStore(storeName);
// 监听事务完成与失败
tx.oncomplete = () => resolve();
tx.onabort = tx.onerror = () => reject(tx.error || new Error('Transaction failed'));
// 执行用户操作(如 store.get, store.put)
try {
const result = fn(store);
if (result instanceof IDBRequest) {
result.onsuccess = () => resolve(result.result);
result.onerror = () => reject(result.error);
} else {
resolve(result);
}
} catch (err) {
reject(err);
}
});
}
基于它封装常用方法:
-
get:
withTransaction(db, 'users', 'readonly', s => s.get(123)) -
put:
withTransaction(db, 'users', 'readwrite', s => s.put({id: 123, name: 'Alice'})) -
delete:
withTransaction(db, 'users', 'readwrite', s => s.delete(123))
处理游标遍历(cursor)也用 Promise 封装
遍历大量数据时需用游标(openCursor),原生写法容易陷入回调地狱。可用递归 Promise 封装成数组返回:
async function getAll(db, storeName) {
const results = [];
return new Promise((resolve, reject) => {
const tx = db.transaction(storeName, 'readonly');
const store = tx.objectStore(storeName);
const req = store.openCursor();
req.onsuccess = () => {
const cursor = req.result;
if (cursor) {
results.push(cursor.value);
cursor.continue(); // 继续下一项
} else {
resolve(results);
}
};
req.onerror = () => reject(req.error);
});
}
或更简洁地用 async/await + 递归(适合小数据量):
async function getAllAsync(db, storeName) {
const results = [];
function walk(cursor) {
if (!cursor) return Promise.resolve();
results.push(cursor.value);
return cursor.continue().then(() => walk(cursor));
}
const tx = db.transaction(storeName, 'readonly');
const req = tx.objectStore(storeName).openCursor();
await new Promise((res, rej) => {
req.onsuccess = () => walk(req.result).then(res);
req.onerror = rej;
});
return results;
}
错误处理与实际使用建议
- 所有 Promise 包装都应保留原始
IDBRequest的error,避免吞掉底层错误信息 - 事务失败(如写入时键冲突、存储空间不足)会触发
tx.onerror或tx.onabort,务必监听并 reject - 不要在
onsuccess中直接 return 值——它不生效;必须用resolve(value) - 对频繁读写场景,可复用已打开的数据库实例,避免反复调用
openDB - 考虑使用成熟封装库如 idb(轻量、TypeScript 友好、自动重试),生产环境推荐优先采用











