Explain how a generator is used in this JavaScript code with IndexedDB?

允我心安 提交于 2019-12-03 07:21:30

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.

The grabEventAndContinueHandler() is littered all over the place in the IDB tests in the Mozilla codebase, but I can't find a definition beyond a couple of these:

function grabEventAndContinueHandler(event) {
  testGenerator.send(event);
} 

Without a function definition I can't say what it does but I'd have to guess they're part of the test suite and pass event messages as these other ones do. yield appears to be a global, perhaps which passes results back from the test suite from inside its grabEventAndContinueHandler().


I would guess that yield here is just a global object that gets set in grabEventAndContinueHandler with the event result from the createObjectStore, objectStore.add() and objectStore.get invocations.

In case it's helpful, I'll give you some background on the use of the yield concept in Ruby. It's sort of like a map() -- it's a keyword that passes messages back to a "block" of code outside of the iterator.

I can't say what yield doing here with certainty (it doesn't seem to be a function), but here's a shot based on my knowledge of IndexedDB.

Given this deals with IDB, I know the yield object here contains the event object (let event = yield), an object which contains the event.target.result attribute.

Since that event attribute only comes from an onsuccess callback, and here request.onsuccess = grabEventAndContinueHandler, I can guess that grabEventAndContinueHandler is the equivalent of the "block" of code and the resulting event object is "yielded" back to the main thread by setting this global object.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!