Skip to content

IndexedDB - ECMAScript 绑定

使用 Promises 和 Async/Await 现代化 IndexedDB

Section titled “使用 Promises 和 Async/Await 现代化 IndexedDB”

原生 IndexedDB API 功能强大,但其基于事件的回调风格可能过于冗长,并导致代码深度嵌套,通常被称为“回调地狱”(callback hell)。现代 JavaScript (ECMAScript 2017+) 提供了 Promises 和 async/await 等特性,可以极大地简化异步代码,使其更具可读性和可维护性。

考虑一个简单的任务:打开数据库,添加一个项目,然后将其读回。使用原生 API,这需要嵌套 onsuccess 处理程序,并且在每个层级都有单独的 onerror 处理程序。

const request = indexedDB.open('myDB', 1);
request.onsuccess = (e) => {
const db = e.target.result;
const tx = db.transaction('store', 'readwrite');
const store = tx.objectStore('store');
const addRequest = store.add({ id: 1, name: 'test' });
addRequest.onsuccess = () => {
console.log('Added!');
// 现在我们来读取它...
const getRequest = store.get(1);
getRequest.onsuccess = (e) => {
console.log('Got item:', e.target.result);
}
getRequest.onerror = (e) => { /* 处理获取错误 */ };
}
addRequest.onerror = (e) => { /* 处理添加错误 */ };
};

这种代码难以阅读,对于更复杂的序列也更难管理。

Promise 是一种表示异步操作最终完成(或失败)的对象。我们可以创建一个辅助函数,它接受一个 IDBRequest 并返回一个 Promise。

function promisifyRequest(request) {
return new Promise((resolve, reject) => {
request.onsuccess = (event) => {
resolve(event.target.result);
};
request.onerror = (event) => {
reject(event.target.error);
};
});
}

这个简单的包装器允许我们使用 .then() 处理成功,使用 .catch() 处理错误,这是一个巨大的改进。

async/await 语法建立在 Promises 之上,它允许我们编写看起来和行为都像同步代码的异步代码。这是 JavaScript 中处理异步操作的现代标准。

让我们将“添加和获取”任务重构为一个清晰的 async 函数。请注意 try...catch 如何提供统一的错误处理。

// 上面的辅助函数
function promisifyRequest(request) {
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
// 我们还需要 Promise 化事务
function promisifyTransaction(tx) {
return new Promise((resolve, reject) => {
tx.oncomplete = resolve;
tx.onerror = () => reject(tx.error);
});
}
async function addAndGetItem(db, item) {
try {
const tx = db.transaction('store', 'readwrite');
const store = tx.objectStore('store');
await promisifyRequest(store.add(item));
console.log('Item added!');
const retrievedItem = await promisifyRequest(store.get(item.id));
console.log('Retrieved item:', retrievedItem);
await promisifyTransaction(tx);
console.log('Transaction complete.');
return retrievedItem;
} catch (error) {
console.error('An error occurred:', error);
throw error; // 重新抛出错误以供调用者处理
}
}
// 用法:
// const db = await promisifyRequest(indexedDB.open('myDB', 1));
// await addAndGetItem(db, { id: 1, name: 'test' });

尽管编写自己的 Promise 包装器是一个很好的学习练习,但对于实际项目,强烈建议使用经过良好测试的包装库。最流行的是 Jake Archibald 的 idb 库。

它提供了一个轻量级的、基于 Promise 的 IndexedDB 封装层,使所有操作变得异常简单,同时在需要时仍能完全访问底层 API。你可以通过 npm 将其添加到你的项目中:npm install idb。

比较前面示例的冗长与使用 idb 的此版本的优雅和清晰。

import { openDB } from 'idb';
async function addAndGetItemWithIdb(item) {
try {
const db = await openDB('myDB', 1, {
upgrade(db) {
db.createObjectStore('store', { keyPath: 'id' });
},
});
// 该库为你处理事务!
await db.add('store', item);
console.log('Item added!');
const retrievedItem = await db.get('store', item.id);
console.log('Retrieved item:', retrievedItem);
return retrievedItem;
} catch (error) {
console.error('An error occurred:', error);
}
}
// 用法:
// await addAndGetItemWithIdb({ id: 1, name: 'test' });

对于任何认真的 IndexedDB 开发,采用 async/await 等现代实践并使用像 idb 这样的辅助库不仅仅是建议——它对于构建健壮且可维护的应用程序至关重要。