Skip to content

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, 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 idb

Then, import it into your JavaScript module:

import { openDB } from 'idb';

The idb library provides several helpful shortcuts and abstractions.

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');
},
});

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 key
const value = await db.get('articles', 'article-1');
// Add a new value
await db.put('articles', { id: 'article-2', title: 'My Second Article', date: new Date() });
// Iterate over a store with a cursor
let cursor = await db.transaction('articles').store.openCursor();
while (cursor) {
console.log(cursor.key, cursor.value);
cursor = await cursor.continue();
}

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 transaction
await 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!');

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);
}
}