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 I'd say that it's an excellent trade off.
The first line of the code creates a generator from the function testSteps
and assigns it to the variable testGenerator
. 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:
var name = "Test";
var version = 1.0;
var description = "Test database.";
var request = mozIndexedDB.open(name, version, description);
request.onupgradeneeded = function (event) {
var db = event.target.result;
var objectStore = db.createObjectStore("Thing", {
keyPath: "id",
autoIncrement: true
});
var object = {
attributeA: 1,
attributeB: 2,
attributeC: 3
};
var request = objectStore.add(object, "uniqueID");
request.onsuccess = function (event) {
var id = event.target.result;
if (id === "uniqueID") alert("Object stored.");
db.close();
};
};
In the above code we requested for a database named Test
. We requested for the database version 1.0
. Since it didn't exist the onupgradeneeded
event 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:
var gen = (function (name, version, description) {
var request = mozIndexedDB.open(name, version, description);
request.onupgradeneeded = grabEventAndContinueHandler;
var event = yield;
var db = event.target.result;
var objectStore = db.createObjectStore("Thing", {
keyPath: "id",
autoIncrement: true
});
var object = {
attributeA: 1,
attributeB: 2,
attributeC: 3
};
request = objectStore.add(object, "uniqueID");
request.onsuccess = grabEventAndContinueHandler;
event = yield;
var id = event.target.result;
if (id === "uniqueID") alert("Object stored.");
db.close();
}("Test", 1.0, "Test database."));
The grabEventAndContinueHandler
function is defined after the generator as follows:
function grabEventAndContinueHandler(event) {
gen.send(event);
}
The generator is started as follows:
gen.next();
Once the generator is started a request is made to open a connection to the given database. Then grabEventAndContinueHandler
is attached as an event handler to the onupgradeneeded
event. Finally we yield or pause the generator using the keyword yield
.
The generator is automatically resumed when the gen.send
method is called from the grabEventAndContinueHandler
function. This function simply takes a single argument called event
and sends it to the generator. When the generator is resumed the sent value is stored in a variable called event
.
To recap, the magic happens here:
// resume the generator when the event handler is called
// and send the onsuccess event to the generator
request.onsuccess = grabEventAndContinueHandler;
// pause the generator using the yield keyword
// and save the onsuccess event sent by the handler
var event = yield;
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With