IndexedDB
JavaScript
synchronous call
async programming
web development

how to make synchronous call to indexeddb method from javascript

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

IndexedDB is asynchronous by design — there is no way to make a truly synchronous call to it from the main thread. The browser intentionally prevents synchronous database access to avoid blocking the UI. The solution is to use Promises and async/await to write code that reads like synchronous code but executes asynchronously. If you genuinely need synchronous IndexedDB access, it was available in Web Workers via indexedDB.openSync() in an early spec, but no browser ever implemented it.

Why IndexedDB Is Async-Only

IndexedDB operations involve disk I/O. A synchronous disk read on the main thread would freeze the page — no scrolling, no animations, no click handlers — until the read completes. This can take 1-100ms or more. The browser API forces asynchronous access to guarantee a responsive UI.

javascript
1// IndexedDB uses request objects with onsuccess/onerror callbacks
2const request = db.transaction('store').objectStore('store').get('key');
3request.onsuccess = () => console.log(request.result);
4request.onerror = () => console.error(request.error);

Wrapping IndexedDB in Promises

Convert the callback-based API into Promises:

javascript
1function openDatabase(name, version, upgradeCallback) {
2    return new Promise((resolve, reject) => {
3        const request = indexedDB.open(name, version);
4        request.onupgradeneeded = (event) => upgradeCallback(event.target.result);
5        request.onsuccess = () => resolve(request.result);
6        request.onerror = () => reject(request.error);
7    });
8}
9
10function getItem(db, storeName, key) {
11    return new Promise((resolve, reject) => {
12        const tx = db.transaction(storeName, 'readonly');
13        const store = tx.objectStore(storeName);
14        const request = store.get(key);
15        request.onsuccess = () => resolve(request.result);
16        request.onerror = () => reject(request.error);
17    });
18}
19
20function putItem(db, storeName, value) {
21    return new Promise((resolve, reject) => {
22        const tx = db.transaction(storeName, 'readwrite');
23        const store = tx.objectStore(storeName);
24        const request = store.put(value);
25        request.onsuccess = () => resolve(request.result);
26        request.onerror = () => reject(request.error);
27    });
28}
29
30function getAllItems(db, storeName) {
31    return new Promise((resolve, reject) => {
32        const tx = db.transaction(storeName, 'readonly');
33        const store = tx.objectStore(storeName);
34        const request = store.getAll();
35        request.onsuccess = () => resolve(request.result);
36        request.onerror = () => reject(request.error);
37    });
38}

Using async/await (Looks Synchronous)

javascript
1async function main() {
2    // Open database
3    const db = await openDatabase('myDB', 1, (db) => {
4        db.createObjectStore('users', { keyPath: 'id' });
5    });
6
7    // Write — reads like synchronous code
8    await putItem(db, 'users', { id: 1, name: 'Alice', age: 30 });
9    await putItem(db, 'users', { id: 2, name: 'Bob', age: 25 });
10
11    // Read
12    const user = await getItem(db, 'users', 1);
13    console.log(user); // { id: 1, name: 'Alice', age: 30 }
14
15    // Get all
16    const allUsers = await getAllItems(db, 'users');
17    console.log(allUsers); // [{ id: 1, ... }, { id: 2, ... }]
18}
19
20main();

This code is asynchronous under the hood but reads top-to-bottom like synchronous code.

The idb library by Jake Archibald wraps IndexedDB with Promises automatically:

javascript
1import { openDB } from 'idb';
2
3async function main() {
4    const db = await openDB('myDB', 1, {
5        upgrade(db) {
6            db.createObjectStore('users', { keyPath: 'id' });
7        },
8    });
9
10    // Write
11    await db.put('users', { id: 1, name: 'Alice', age: 30 });
12
13    // Read
14    const user = await db.get('users', 1);
15    console.log(user);
16
17    // Get all
18    const all = await db.getAll('users');
19    console.log(all);
20
21    // Transaction
22    const tx = db.transaction('users', 'readwrite');
23    await Promise.all([
24        tx.store.put({ id: 3, name: 'Charlie' }),
25        tx.store.put({ id: 4, name: 'Diana' }),
26        tx.done,
27    ]);
28}

Install with npm install idb. It is only 1.2KB gzipped.

Cursor Iteration with Promises

javascript
1function iterateStore(db, storeName, callback) {
2    return new Promise((resolve, reject) => {
3        const tx = db.transaction(storeName, 'readonly');
4        const store = tx.objectStore(storeName);
5        const request = store.openCursor();
6        const results = [];
7
8        request.onsuccess = (event) => {
9            const cursor = event.target.result;
10            if (cursor) {
11                results.push(callback(cursor.value));
12                cursor.continue();
13            } else {
14                resolve(results);
15            }
16        };
17        request.onerror = () => reject(request.error);
18    });
19}
20
21// Usage
22const names = await iterateStore(db, 'users', user => user.name);
23console.log(names); // ['Alice', 'Bob', 'Charlie']

Pre-Loading Data for Synchronous Access

If you need synchronous reads in a hot path, load data into memory first:

javascript
1class SyncCache {
2    constructor() {
3        this.cache = new Map();
4    }
5
6    async loadFromDB(db, storeName) {
7        const items = await getAllItems(db, storeName);
8        for (const item of items) {
9            this.cache.set(item.id, item);
10        }
11    }
12
13    // Synchronous read from memory cache
14    get(key) {
15        return this.cache.get(key);
16    }
17
18    // Async write-through to IndexedDB
19    async set(db, storeName, value) {
20        this.cache.set(value.id, value);
21        await putItem(db, storeName, value);
22    }
23}
24
25// Initialize once
26const cache = new SyncCache();
27await cache.loadFromDB(db, 'users');
28
29// Now reads are truly synchronous
30const user = cache.get(1); // No await needed

Common Pitfalls

  • Expecting synchronous access: No browser supports synchronous IndexedDB on the main thread. The IDBFactory.openSync() from the early spec was never implemented. Do not look for it.
  • Not awaiting transactions: IndexedDB transactions auto-commit when all requests are done. If you start a transaction and do async work before using it, the transaction may already be committed. Keep all operations within the same microtask.
  • Error handling: IndexedDB errors propagate via onerror callbacks, not exceptions. Without Promise wrappers, errors are silently lost. Always add error handlers or use the idb library.
  • Version management: indexedDB.open(name, version) only triggers onupgradeneeded when the version increases. Forgetting to bump the version means schema changes are silently ignored.
  • Blocking the page with large reads: Even async reads can cause jank if you process millions of records in a tight loop. Use cursors with requestAnimationFrame or Web Workers for large datasets.

Summary

  • IndexedDB has no synchronous API on the main thread — this is by design
  • Wrap IndexedDB requests in Promises and use async/await for synchronous-looking code
  • Use the idb library for a clean, Promise-based IndexedDB API
  • For truly synchronous access, pre-load data into a memory cache
  • Always handle errors — IndexedDB silently drops errors without proper handlers

Course illustration
Course illustration

All Rights Reserved.