Skip to content

Instantly share code, notes, and snippets.

@inexorabletash
Last active July 26, 2016 17:18
Show Gist options
  • Save inexorabletash/9675881 to your computer and use it in GitHub Desktop.
Save inexorabletash/9675881 to your computer and use it in GitHub Desktop.
Indexed DB Promises #2

STATUS: Obsolete - see this updated proposal instead.

Extend IDBDatabase with:

interface IDBDatabase {
  IDBExplicitTransaction explicitTransaction(scope, mode);
};

Which gives you one of these:

interface IDBExplicitTransaction : IDBTransaction {
  Promise<undefined> commit(); // Resolves if committed, rejects if aborted; idempotent
};

An explicit transaction remains active until it aborts due to a request error event on which preventDefault() is not called, or abort() or commit() is explicitly called by script.

This allows you to write things such as:

function queryFetchAndStore(key) {
  return new Promise(resolve, reject) {
    var tx = db.explicitTransaction(['urls', 'blobs'], 'readwrite');
    var r = tx.objectStore('urls').get(key);
    r.onerror = reject;
    r.onsuccess = function() {
      // If tx were not explicit, it would autocommit when we went async here.
      fetchAsBlob(r.result).then(function(blob) {
        // ... and even if not, the tx would not be active here so would throw
        tx.objectStore('blobs').put(blob, key);
        tx.commit().then(resolve, reject);
      }, reject);
    };
  };   
}

That's still pretty gnarly entangling of Promises with Events. Let's clean it up a bit.

interface IDBRequest {
  Promise<any> ready();    // Returns new Promise that resolves/rejects with success/error
  Promise<any> then(f, g); // Shortcut for ready().then(f, g);
  Promise<any> catch(f);   // Shortcut for ready().catch(f);
};

With the caveat that ready() throws unless the request's transaction is explicit. Maybe? Otherwise the transaction is inactive inside the resolve/reject callbacks.

(Also there's probably a caveat about cursors.)

Now you can write:

function queryFetchAndStore(key) {
  var tx = db.explicitTransaction(['urls', 'blobs'], 'readwrite');
  return tx.objectStore('urls').get(key).then(function(url) {
    return fetchAsBlob(url);
  }).then(function(blob) {
    tx.objectStore('blobs').put(blob, key);
    return tx.commit();
  });
}

This is optional sugar:

interface IDBTransaction {
  Promise<any> then(f, g); // Shortcut for commit().then(f, g);
  Promise<any> catch(f); // Shortcut for commit().catch(f);
};

Issues

  • An explicit transaction presumably aborts when it is GC'd. Does this make GC observable, e.g. tx.addEventListener('abort', function() { alert('gc!'); });
    • We'd probably have to let them get GC without firing the event (otherwise, what is event.target). But GC would still be observable if there was another transaction blocked on it.
    • Something similar applies to the event.waitUntil(p) primitive in Service Workers.
    • Hrm... this is already sort-of exposed by IDB:
indexedDB.open('db', 1).onsuccess = function(e) { var db = e.target.result; db = null; };
indexedDB.open('db', 2).onsuccess = function() { alert('db was collected'); };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment