17

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?

Adria
  • 8,651
  • 4
  • 37
  • 30

3 Answers3

16

Opening the IDB every time the ServiceWorker starts up is unlikely to be optimal, you'll end up opening it even when it isn't used. Instead, open the db when you need it. A singleton is really useful here (see https://github.com/jakearchibald/svgomg/blob/master/src/js/utils/storage.js#L5), so you don't need to open IDB twice if it's used twice in its lifetime.

The "activate" event is a great place to open IDB and let any "onupdateneeded" events run, as the old version of ServiceWorker is out of the way.

JaffaTheCake
  • 13,895
  • 4
  • 51
  • 54
13

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.

Joshua Bell
  • 7,727
  • 27
  • 30
  • It is worth checking the following small library that mirrors the indexedDb: https://github.com/jakearchibald/indexeddb-promised – magiccrafter Jun 29 '16 at 10:26
  • 1
    Hello from the future! The linked ("promised") library doesn't look that popular on NPM and I haven't seen any changes to the spec. As somebody who started serious JS development right around the time the Promise spec stabilized, my first thought on reading the IDB spec was "they *can't* be serious". Has there been any traction on an *official* modern (callback-based / Promise-based, not event-handler-based) version of this API? – Coderer Jan 24 '18 at 12:38
4

I don't know of anything special about accessing IndexedDB from the context of a service worker via accessing IndexedDB via a controlled page.

Promises obviously makes your life much easier within a service worker, so I've found using something like, e.g., https://gist.github.com/inexorabletash/c8069c042b734519680c to be useful instead of the raw IndexedDB API. But it's not mandatory as long as you create and manage your own promises to reflect the state of the asynchronous IndexedDB operations.

The main thing to keep in mind when writing a fetch event handler (and this isn't specific to using IndexedDB), is that if you call event.respondWith(), you need to pass in either a Response object or a promise that resolves with a Response object. As long as you're doing that, it shouldn't matter whether your Response is constructed from IndexedDB entries or the Cache API or elsewhere.

Are you running into any actual problems with the code you posted, or was this more of a theoretical question?

Jeff Posnick
  • 53,580
  • 14
  • 141
  • 167
  • Theoretical. The code works fine, but only because the onsuccess event always "wins". Promisifying indexedDB is probably the right solution. – Adria Mar 23 '15 at 22:07
  • 1
    If you don't make use of transactions and just use IndexedDB like localStorage, something like that gist I linked to seems to work well enough. – Jeff Posnick Mar 24 '15 at 00:36
  • That is true, but it really limits the applications of IndexedDB - you lose the ability to group operations into atomic transactions and it is really slow when everything is in its own transaction. – dumbmatter Mar 24 '15 at 01:21
  • Yes. But if you are using it in a SW, you may not have a better choice. And it can still prove useful with those limitations. – Jeff Posnick Mar 24 '15 at 01:23
  • I hope there are better choices, because I wanted to try some crazy things with IDB in a ServiceWorker! And there must be. Because regardless of whether you wrap IDB in promises or not, you still have the fundamental issue that the OP raised: how can you guarantee your IDB connection will open before `onfetch` is called? – dumbmatter Mar 24 '15 at 02:12
  • 2
    You should be able to open the IDB connection within the fetch handler, as long as you wrap that operation in a promise which you then use as part of the promise chain passed to event.respondWith() – Jeff Posnick Mar 24 '15 at 02:28