Accessing indexedDB in ServiceWorker. Race condition

Adria picture Adria · Mar 23, 2015 · Viewed 10.9k times · Source

There aren't many examples demonstrating indexedDB in a ServiceWorker yet, but the ones I saw were all structured like this:

const request = indexedDB.open( 'myDB', 1 );
var db;

request.onupgradeneeded = ...

request.onsuccess = function() {
    db = this.result; // Average 8ms
};


self.onfetch = function(e)
{
    const requestURL = new URL( e.request.url ),
    path = requestURL.pathname;

    if( path === '/test' )
    {
        const response = new Promise( function( resolve )
        {
            console.log( performance.now(), typeof db ); // Average 15ms

            db.transaction( 'cache' ).objectStore( 'cache' ).get( 'test' ).onsuccess = function()
            {
                resolve( new Response( this.result, { headers: { 'content-type':'text/plain' } } ) );
            }
        });

        e.respondWith( response );
    }
}

Is this likely to fail when the ServiceWorker starts up, and if so what is a robust way of accessing indexedDB in a ServiceWorker?

Answer

Joshua Bell picture Joshua Bell · Mar 24, 2015

You can wrap a transaction in a promise like so:

var tx = db.transaction(scope, mode);
var p = new Promise(function(resolve, reject) {
  tx.onabort = function() { reject(tx.error); };
  tx.oncomplete = function() { resolve(); };
});

Now p will resolve/reject when the transaction completes/aborts. So you can do arbitrary logic in the tx transaction, and p.then(...) and/or pass a dependent promise into e.respondWith() or e.waitUntil() etc.

As noted by other commenters, we really do need to promisify IndexedDB. But the composition of its post-task autocommit model and the microtask queues that Promises use make it... nontrivial to do so without basically completely replacing the API. But (as an implementer and one of the spec editors) I'm actively prototyping some ideas.