IndexedDB - Promise Wrapper
IndexedDB: Modern Asynchronous Operations with Promise Wrappers
Section titled “IndexedDB: Modern Asynchronous Operations with Promise Wrappers”The native IndexedDB API is powerful but notoriously verbose and complex due to its event-based asynchronous model. In modern JavaScript, Promises, particularly with the async/await syntax, provide a much cleaner and more intuitive way to handle asynchronous operations without blocking the main thread.
Instead of attaching onsuccess and onerror event handlers to every request, we can use a promise-based wrapper. This allows us to write asynchronous database logic that reads like synchronous code, significantly improving readability and maintainability.
The idb Library: The Modern Standard
Section titled “The idb Library: The Modern Standard”The idb library, created by Jake Archibald, is the de-facto standard for working with IndexedDB in a modern development workflow. It’s a lightweight, promise-based wrapper that simplifies the native API while preserving its powerful features. It makes IndexedDB feel like a modern, developer-friendly tool.
You can add it to your project using a package manager like npm:
npm install idbThen, import it into your JavaScript module:
import { openDB } from 'idb';Key Enhancements with idb
Section titled “Key Enhancements with idb”The idb library provides several helpful shortcuts and abstractions.
Simplified Database Opening
Section titled “Simplified Database Opening”Opening a database and handling version upgrades becomes much cleaner.
import { openDB } from 'idb';
const db = await openDB('my-database', 1, { upgrade(db, oldVersion, newVersion, transaction) { // This is the only place to create and modify the database's schema const store = db.createObjectStore('articles', { keyPath: 'id' }); store.createIndex('date', 'date'); },});Promise-Based Transactions and Cursors
Section titled “Promise-Based Transactions and Cursors”Transactions and cursors, which are complex in the native API, are returned as promises, making them easy to use with async/await.
// Get a value by keyconst value = await db.get('articles', 'article-1');
// Add a new valueawait db.put('articles', { id: 'article-2', title: 'My Second Article', date: new Date() });
// Iterate over a store with a cursorlet cursor = await db.transaction('articles').store.openCursor();
while (cursor) { console.log(cursor.key, cursor.value); cursor = await cursor.continue();}Transaction done Promise
Section titled “Transaction done Promise”The idb transaction object has a .done property, which is a promise that resolves when the transaction completes successfully or rejects if it fails. This is useful for coordinating multiple operations.
const tx = db.transaction('articles', 'readwrite');
// Perform multiple operations concurrently within the same transactionawait Promise.all([ tx.store.add({ id: 'article-3', title: '...' }), tx.store.put({ id: 'article-4', title: '...' }), tx.done, // This promise ensures the transaction is complete]);
console.log('Transaction finished!');Debugging Tip
Section titled “Debugging Tip”When using async/await with idb, always wrap your database operations in try...catch blocks to handle potential errors, such as transaction failures or constraint violations.
async function addArticle(article) { try { const db = await openDB('my-database', 1); await db.add('articles', article); console.log('Article added successfully!'); } catch (error) { console.error('Failed to add article:', error); }}