"When in doubt, WebIDL"

Yesterday I was fixing a bug in some IndexedDB code we use for Thimble's browser-based filesystem. When I'm writing code against an API like IndexedDB, I love knowing the right way to use it. The trouble is, it can be difficult to find the proper use of an API, or in my case, its edge cases.

Let's take a simple question: should I wrap certain IndexedDB methods in try...catch blocks? In other words, will this code ever throw?

There are a number of obvious ways to approach this problem, from reading the docs on MDN to looking for questions/answers on StackOverflow to reading open source code on GitHub for projects that do similar things to you.

I did all of the above, and the problem is that people don't always know, or at least don't always bother to do the right thing in all cases. Some IndexedDB wrapper libraries on GitHub I read do use try...catch, others don't; some docs discuss it, others glaze over it. Even DOM tests I read sometimes use it, and sometimes don't bother. I hate that level of ambiguity.

Another option is to ask someone smart, which I did yesterday. I thought the reply I got from @asutherland was a great reminder of something I'd known, but didn't think to use in this case:

"When in doubt, WebIDL"

WebIDL is an Interface Definition Language (IDL) for the web that defines interfaces (APIs), their methods, properties, etc. It also clearly defines what might throw an error at runtime.

While implementing DOM interfaces in Gecko, I've had to work with IDL in the past; however, it hadn't occurred to me to consult it in this case, as a user of an API vs. its implementer. I thought it was an important piece of learning, so I wanted to write about it.

Let's take the case of opening a database using IndexedDB. To do this, you need to do something like this:

var db;  
var request = window.indexedDB.open("data");  
request.onerror = function(event) {  
  handleError(request.error);
};
request.onsuccess = function() {  
  db = request.result;
};

Now, given that indexedDB.open() returns an IDBOpenDBRequest object to which one can attach an onerror handler, it's easy to think that you've done everything correctly simply by handling the error case.

However, window.indexedDB implements the IDBFactory interface, so we can also check its WebIDL definition to see if the open method might also throw. It turns out that it does:

/**
 * Interface that defines the indexedDB property on a window.  See
 * http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#idl-def-IDBFactory
 * for more information.
 */
[Exposed=(Window,Worker,System)]
interface IDBFactory {  
  [Throws, NeedsCallerType]
  IDBOpenDBRequest
  open(DOMString name,
       [EnforceRange] unsigned long long version);
...

This is really useful to know, since we should really be wrapping this call in a try...catch if we care about failing in ways to which a user can properly respond (e.g., showing error messages, retrying, etc.).

Why might it throw? You can see for yourself in the source for WebKit or Gecko, everything from:

  • Passing a database version number of 0
  • Not specifying a database name
  • Failing a security check (e.g., origin check)
  • The underlying database can't be opened for some reason

The list goes on. Try it yourself, by opening your dev console and entering some code that is known to throw, based on what we just saw in the code (e.g., no database name, using 0 as a version):

indexedDB.open()  
Uncaught TypeError: Failed to execute 'open' on 'IDBFactory': 1 argument required, but only 0 present.  
...
indexedDB.open("name", 0)  
Uncaught TypeError: Failed to execute 'open' on 'IDBFactory': The version provided must not be 0.  

The point is, it might throw in certain cases, and knowing this should guide your decisions about how to use it. The work I'm doing relates to edge case uses of a database, in particular when disk space is low, the database is corrupted, and other things we're still not sure about. I want to handle these errors, and try to guide users toward a solution. To accomplish that, I need try...catch.

When in doubt, WebIDL.