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

After going through the wonderful world of IndexedDB, I came across code, for example from the Mozilla 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, unlike the typical doom pyramid style that you see with IndexedDB because asynchronous callbacks are stacked together (and, of course, generators are not widely supported outside of Firefox .. )

So, this code from Mozilla is somewhat attractive and intriguing for me, as it looks very clean, but I'm not quite sure what yield does in this context. Can someone help me figure this out?

+6
source share
2 answers

This is a brilliant piece of code that takes advantage of the powerful new JavaScript 1.7 features opened by Firefox, and since IndexedDB is only supported by Firefox and Chrome, I would say this is a great compromise.

The first line of code creates a generator from the testSteps function and assigns it to the testGenerator variable. The reason we use generators is because IndexedDB is a purely asynchronous API; and asynchronous programming and nested callbacks are a pain. Using generators alleviates this pain by letting you write asynchronous code that looks synchronous.

Note. . If you want to know how to use the power of generators for synchronous asynchronous code, read the next article .

To explain how generators are useful for providing asynchronous programming, 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 a database called Test . We requested database version 1.0 . Since it did not exist, the onupgradeneeded event onupgradeneeded was started. As soon as we received the database, we created an object store on it, added the object to the object store, and after saving it, we closed the database.

The problem with the above code is that we query the database and perform other operations related to it asynchronously. This can make the code very difficult to maintain as more and more nested callbacks are used.

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 grabEventAndContinueHandler defined after the generator as follows:

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

The generator starts as follows:

 gen.next(); 

After starting the generator, a request is created to open a connection to this database. Then, grabEventAndContinueHandler attached as an event handler to the onupgradeneeded event. Finally, we return or pause the generator using the yield keyword.

The generator automatically resumes when the gen.send method gen.send called from the grabEventAndContinueHandler function. This function simply takes one argument named event and sends it to the generator. When the generator resumes, the passed value is stored in a variable called event .

To repeat, 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 allows you to write asynchronous code as if it were synchronous. To learn more about generators, read the next MDN article . Hope this helps.

+4
source

grabEventAndContinueHandler() fits all over the place in the IDB tests in the Mozilla codebase, but I cannot find a definition outside of a pair of these :

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

Without defining a function, I cannot say what it does, but I had to assume that they are part of a test suite and transmit event messages like others do. yield appears to be global, possibly one that returns results from a test suite from its grabEventAndContinueHandler() .


I would suggest that yield here is just a global object that is set to grabEventAndContinueHandler with the result of an event from createObjectStore , objectStore.add() and objectStore.get invocations.

In case this is useful, I will tell you about using the yield concept in Ruby. This is similar to map() - this is a keyword that passes messages back to the "block" of code outside the iterator.

I can’t say what yield does here with certainty (this doesn’t seem like a function), but here is a frame based on my knowledge of IndexedDB.

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

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

+1
source

Source: https://habr.com/ru/post/908100/


All Articles