jakearchibald / idb

IndexedDB, but with promises
https://www.npmjs.com/package/idb
ISC License
6.29k stars 353 forks source link

Safari throws "TransactionInactiveError: Failed to store record in an IDBObjectStore: The transaction is inactive or finished." #201

Closed Jack-Works closed 3 years ago

Jack-Works commented 3 years ago

import('https://cdn.skypack.dev/idb@5.0.6/with-async-ittr').then(async (idb) => {
    await idb.deleteDB('test')
    const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
    const openDB = (() => {
        /** @type {import('idb').IDBPDatabase<unknown>} */
        let db = undefined
        return async () => {
            if (db) return db
            if (!db)
                db = await idb.openDB('test', 1, {
                    upgrade(db, oldVersion, newVersion, transaction) {
                        db.createObjectStore('store')
                    },
                })
            db.addEventListener('close', () => (db = undefined))
            return db
        }
    })()
    /** @type {import('idb').IDBPTransaction<unknown, ["store"]>} */
    let transaction = undefined
    const beforeTx = async () => {
        try {
            await transaction.objectStore('store').openCursor(IDBKeyRange.only('a'))
            console.log('The transaction is alive!')
        } catch (e) {
            console.log('Transaction outdated', e, 'creating new one')
            transaction = (await openDB()).transaction('store', 'readwrite')
        }
    }
    await beforeTx()
    await transaction.store.add({ a: 1 }, 'a')
    console.log('added a')
    await beforeTx()
    await transaction.store.add({ a: 1 }, 'b')
    console.log('added b')

    await sleep(120)
    await beforeTx()
    await transaction.store.add({ a: 1 }, 'c')
    console.log('added c')

    await sleep(120)
    console.log('before call beforeTx')
    await beforeTx()
    console.log('after call beforeTx')
    const cursor = await transaction.store.openCursor(IDBKeyRange.only('a'))
    console.log(cursor.value)
    const next = await cursor.continue()
    console.log(next)
})

Chrome and Firefox no problem. But Safari throws.

(The last Unhandled Promise Rejection).

The expected result is (on Chrome):

Jack-Works commented 3 years ago

I believe it is a Safari bug, here is the tracking issue https://bugs.webkit.org/show_bug.cgi?id=216769

jakearchibald commented 3 years ago

Thanks for filing this on WebKit, I agree it looks like a browser bug. I recommend trying to reduce the demo to just the failing test, and even try to recreate it without the library.

Jack-Works commented 3 years ago

I have found a workaround for anyone have the same bug.

You should try catch the transaction after it created immediately, if failed, re create the transaction.

I do want to reduce the dependency but the indexed db raw api is too hard to use.