While making my way through the wonderful world of IndexedDB, I came across code like this from Mozilla’s test suite:
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
var testGenerator = testSteps();
function testSteps()
{
const IDBObjectStore = Components.interfaces.nsIIDBObjectStore;
const name = this.window ? window.location.pathname : "Splendid Test";
const description = "My Test Database";
var data = [
{ name: "inline key; key generator",
autoIncrement: true,
storedObject: {name: "Lincoln"},
keyName: "id",
keyValue: undefined,
},
{ name: "inline key; no key generator",
autoIncrement: false,
storedObject: {id: 1, name: "Lincoln"},
keyName: "id",
keyValue: undefined,
},
{ name: "out of line key; key generator",
autoIncrement: true,
storedObject: {name: "Lincoln"},
keyName: undefined,
keyValue: undefined,
},
{ name: "out of line key; no key generator",
autoIncrement: false,
storedObject: {name: "Lincoln"},
keyName: null,
keyValue: 1,
}
];
for (let i = 0; i < data.length; i++) {
let test = data[i];
let request = mozIndexedDB.open(name, i+1, description);
request.onerror = errorHandler;
request.onupgradeneeded = grabEventAndContinueHandler;
let event = yield;
let db = event.target.result;
let objectStore = db.createObjectStore(test.name,
{ keyPath: test.keyName,
autoIncrement: test.autoIncrement });
request = objectStore.add(test.storedObject, test.keyValue);
request.onerror = errorHandler;
request.onsuccess = grabEventAndContinueHandler;
event = yield;
let id = event.target.result;
request = objectStore.get(id);
request.onerror = errorHandler;
request.onsuccess = grabEventAndContinueHandler;
event = yield;
// Sanity check!
is(test.storedObject.name, event.target.result.name,
"The correct object was stored.");
request = objectStore.delete(id);
request.onerror = errorHandler;
request.onsuccess = grabEventAndContinueHandler;
event = yield;
// Make sure it was removed.
request = objectStore.get(id);
request.onerror = errorHandler;
request.onsuccess = grabEventAndContinueHandler;
event = yield;
ok(event.target.result === undefined, "Object was deleted");
db.close();
}
finishTest();
yield;
}
Their other tests are written in a similar style, as opposed to the typical “pyramid of doom” style you see with IndexedDB due to asynchronous callbacks being stacked together (and, of course, generators aren’t widely supported beyond Firefox..).
So, this code from Mozilla is somewhat appealing and intriguing to me as it looks very clean, but I’m not totally sure what yield is doing in this context. Can anyone help me understand this?
This is a brilliant piece of code which leverages the powerful new features of JavaScript 1.7 exposed by Firefox, and since IndexedDB is only supported by Firefox and Chrome (in 2012) I’d say that it’s an excellent trade off (Note that as of at least 2022, indexedDB is supported in all major browsers).
The first line of the code creates a generator from the function
testStepsand assigns it to the variabletestGenerator. The reason we are using generators is because IndexedDB is a purely asynchronous API; and asynchronous programming and nested callbacks are a pain. Using generators eases this pain by allowing you to write asynchronous code that looks synchronous.Note: If you want to know how to leverage the power of generators to make asynchronous code synchronous read the following article.
To explain how generators are useful to make asynchronous programming bearable consider the following code:
In the above code we requested for a database named
Test. We requested for the database version1.0. Since it didn’t exist theonupgradeneededevent handler was fired. Once we got the database we created an object store on it, added an object to the object store, and after it was saved we closed the database.The problem with the above code is that we are requesting for the database and doing other operations related to it asynchronously. This could make the code very difficult to maintain as more and more nested callbacks are employed.
To solve this problem we use generators as follows:
The
grabEventAndContinueHandlerfunction is defined after the generator as follows:The generator is started as follows:
Once the generator is started a request is made to open a connection to the given database. Then
grabEventAndContinueHandleris attached as an event handler to theonupgradeneededevent. Finally we yield or pause the generator using the keywordyield.The generator is automatically resumed when the
gen.sendmethod is called from thegrabEventAndContinueHandlerfunction. This function simply takes a single argument calledeventand sends it to the generator. When the generator is resumed the sent value is stored in a variable calledevent.To recap, the magic happens here:
The above code makes it possible to write asynchronous code as if it were synchronous. To know more about generators read the following MDN article. Hope this helps.