Skip to content

Dexie.PrematureCommitError

David Fahlander edited this page Nov 1, 2016 · 17 revisions

Inheritance Hierarchy

Since 2.0.0

Description

This exception will be thrown when the indexedDB transaction commits before the promise returned by your transaction scope is resolved or rejected.

Solution:

  1. Make sure to never call other async APIs from within a transaction
  2. Always use the global Promise (or Dexie.Promise) inside transactions.

NOT OK:

db.transaction ('rw', db.friends, () => {
    return fetch(someUrl); // fetch() is a non-indexedDB async API.
});

db.transaction ('rw', db.friends, () => {
    return new Promise (resolve => {
        setTimeout(resolve, 100); // setTimeout is non-indexedDB async API.
    });
});

Dont call setTimeout() or any other async API from inside a transaction.

NOT OK:

let Promise = require('bluebird');
db.transaction('r', db.friends, () => {
    return new Promise((resolve, reject) => {
        db.friends.get(1).then(resolve, reject);
    });
});

Don't use 3-rd part promises within transactions. Must only use Dexie.Promise or the built-in promise (window.Promise / self.Promise / global.Promise) within transactions.

THIS IS OK (in Dexie 2.0.0 and above):

db.transaction('r', db.friends, function () {
    // In Dexie 2.0, it's ok to use the global Promise (window.Promise)
    return Promise.all([
        db.friends.get(1),
        db.friends.get(2)
    ]);
});

THIS IS ALSO OK:

db.transaction('r', db.friends, async () => {
    // In Dexie 2.0, it's ok to use the global Promise (window.Promise)
    return await Promise.all([
        db.friends.get(1),
        db.friends.get(2)
    ]);
});

Since Dexie 2.0, you may use the global Promise within transactions, since it will always be temporary patched within the transaction zone. But interactions with non-Dexie API:s must only be done outside transactions. For example if you need to fetch data from a REST API, do that before entering the transaction. And if you need to call REST API based on a database query, do that when your transaction completes.

THIS IS OK:

async function sample() {
    let restData = await fetch(someUrl);
    let dbResult = await db.transaction('rw', db.someTable, async ()=> {
        let changedItems = await db.someTable
            .where('changeDate').above(lastSyncDate)
            .toArray();

        await db.someTable.bulkPut(restData);

        return changedItems;
    });
    await fetch(someUrl, {method: 'POST', body: dbResult});
}

In this trivial sync-sample, the fetch() API is only called from outside the transaction.

Parallell transactions

It's also OK to run several different database transactions in parallell. Transactions are maintained using zones.

THIS IS OK:

let [drdree, snoopy] = await Promise.all([

    db.transaction('r', db.friends, db.friendAddresses, async () => {
        let friend = await db.friends.get("drdree");
        friend.addresses = await db.friendAddresses
            .where('friendId').equals(friend.id)
            .toArray();

        return friend;
    }),

    db.transaction('r', db.pets, db.friends, () => {
        return db.pets.get("snoopy").then (snoopy => {
            return db.friends.get(snoopy.ownerId).then(owner => {
                snoopy.owner = owner;
                return snoopy;
            });
        });
    })

]);

console.log(JSON.stringify(drdree));
console.log(JSON.stringify(snoopy));

The two transactions can run in parallell. Zones will make sure that each time someone uses a table, it will be invoked using that current transaction of that particular flow of async calls.

I intentionally mix async and non-async functions in the last sample just so clarify that both are acceptable.

Clone this wiki locally