2012-06-07 6 views
6

Während mein Weg durch die wunderbare Welt der IndexedDB machen, stieß ich auf Code wie this von Mozillas Test-Suite:Erläutern, wie ein Generator in diesem JavaScript-Code mit IndexedDB verwendet wird?

/** 
* 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; 
} 

Die anderen Tests werden in einem ähnlichen Stil geschrieben, in Bezug auf die typischen „Pyramide des Schicksals gegen "Stil, den Sie mit IndexedDB sehen, weil asynchrone Callbacks zusammen gestapelt sind (und natürlich werden Generatoren nicht weit über Firefox hinaus unterstützt ...).

Also, dieser Code von Mozilla ist etwas ansprechend und faszinierend für mich, wie es sehr sauber aussieht, aber ich bin mir nicht ganz sicher, was yield in diesem Zusammenhang tut. Kann mir jemand helfen, das zu verstehen?

+0

Welche Art von Details kann ich bereitstellen? – buley

+0

Ich bin mir nicht ganz sicher. Ich verstehe immer noch nicht wirklich, was vor sich geht. Als Referenz [hier ist der Begriff grabEventAndContinueHandler definiert] (http://hg.mozilla.org/mozilla-central/file/895e12563245/dom/indexedDB/test/helpers.js). Sagt das irgendwie "wenn du zur" yield "Linie kommst, warte bis das Event beendet ist"? Wie? – dumbmatter

+0

Auch, danke für Ihre ursprüngliche Antwort und Ihre anderen IndexedDB Antworten hier. Du bist einer der wenigen Menschen auf der Welt, die tatsächlich darüber schreiben, wie es benutzt werden sollte. – dumbmatter

Antwort

4

Dies ist ein brillanter Code, der die leistungsstarken neuen Funktionen von JavaScript 1.7 in Firefox nutzt. Da IndexedDB nur von Firefox und Chrome unterstützt wird, würde ich sagen, dass dies ein exzellenter Kompromiss ist. Die erste Zeile des Codes erzeugt einen Generator aus der Funktion testSteps und ordnet ihn der Variablen testGenerator zu. Der Grund, warum wir Generatoren verwenden, ist, dass IndexedDB eine rein asynchrone API ist; und asynchrone Programmierung und verschachtelte Rückrufe sind ein Schmerz. Die Verwendung von Generatoren erleichtert diesen Schmerz, indem Sie asynchronen Code schreiben können, der synchron aussieht.

Hinweis: Wenn Sie wissen möchten, wie Sie die Leistung von Generatoren nutzen können, um asynchronen Code synchron zu machen, lesen Sie following article.

Um zu erklären, wie Generatoren nützlich sind asynchrone Programmierung erträglich den folgenden Code betrachten zu machen:

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 dem obigen Code, den wir für eine Datenbank Test namens angefordert. Wir haben für die Datenbankversion 1.0 angefordert. Da es nicht existierte, wurde der Event-Handler ausgelöst. Sobald wir die Datenbank bekommen haben, haben wir einen Objektspeicher darauf angelegt, ein Objekt zum Objektspeicher hinzugefügt, und nachdem es gespeichert wurde, haben wir die Datenbank geschlossen.

Das Problem mit dem obigen Code besteht darin, dass wir für die Datenbank anfordern und andere asynchrone Vorgänge ausführen. Dies könnte dazu führen, dass der Code sehr schwierig zu pflegen ist, da mehr und mehr verschachtelte Rückrufe verwendet werden.

Zur Lösung dieses Problems wir Generatoren verwenden wie folgt:

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.")); 

Die grabEventAndContinueHandler Funktion nach dem Generator wird wie folgt definiert:

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

Der Generator wird wie folgt gestartet:

gen.next(); 

Sobald der Generator gestartet ist, wird eine Anfrage zum Öffnen von AC gestellt Verbindung mit der angegebenen Datenbank. Dann wird grabEventAndContinueHandler als Ereignishandler an das Ereignis onupgradeneeded angefügt. Schließlich geben oder pausieren wir den Generator mit dem Schlüsselwort yield.

Der Generator wird automatisch wieder aufgenommen, wenn die gen.send-Methode von der grabEventAndContinueHandler-Funktion aufgerufen wird. Diese Funktion nimmt einfach ein einziges Argument namens event und sendet es an den Generator. Wenn der Generator wieder aufgenommen wird, wird der gesendete Wert in einer Variablen namens event gespeichert.

Zur Erinnerung, passiert die Magie hier:

// 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; 

Der obige Code macht es möglich, asynchronen Code zu schreiben, als ob es synchron war. Um mehr über Generatoren zu erfahren, lesen Sie die folgenden MDN article. Hoffe das hilft.

+1

Großartige Erklärung! Eine Sache, die hervorzuheben ist, ist, dass "Ertrag" die Ausführung nicht wirklich "stoppt" oder "pausiert". Es kehrt einfach zum Aufruf von gen.send() oder gen.next() zurück. Der coole Teil ist natürlich, dass Sie die Ausführung fortsetzen, indem Sie gen.send() oder gen.next() erneut aufrufen. Noch cooler ist, dass Ausbeute in If-Anweisungen und Schleifenkonstrukte arbeitet. Das macht es einfach, eine Schleife zu schreiben, die asynchron über einen Cursor läuft. Eine Sache, auf die man achten sollte, ist, dass die von Firefox verwendete Syntax wahrscheinlich nicht zu 100% mit der Syntax übereinstimmt, die ES6 standardisieren wird. Wir werden natürlich aktualisieren, um der Spezifikation zu folgen. –

1

Die grabEventAndContinueHandler() ist all overthe place in den IDB-Tests in der Mozilla-Code-Basis übersät, aber ich kann keine Definition über ein paar these finden:

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

Ohne Funktion Definition, die ich kann nicht sagen, was es tut, aber ich würde erraten müssen, dass sie Teil der Testsuite sind und Ereignisnachrichten wie diese anderen weitergeben. yield scheint ein globaler zu sein, der vielleicht die Ergebnisse aus der Testsuite innerhalb seiner grabEventAndContinueHandler() zurückgibt.


Ich würde vermuten, dass yield hier ist nur ein globales Objekt, das in grabEventAndContinueHandler mit dem Ereignis ergeben sich aus den createObjectStore, objectStore.add() und objectStore.get Anrufungen gesetzt wird.

Falls es hilfreich ist, gebe ich Ihnen einige Hintergrundinformationen über die Verwendung des yield Konzepts in Ruby. Es ist wie ein map() - es ist ein Schlüsselwort, das Nachrichten an einen "Block" von Code außerhalb des Iterators zurückgibt.

Ich kann nicht sagen, was yield hier mit Sicherheit tun (es scheint keine Funktion zu sein), aber hier ist eine Aufnahme basierend auf meiner Kenntnis von IndexedDB.

Da dies IDB behandelt, weiß ich, dass das Ertragsobjekt hier das Ereignisobjekt (let event = yield) enthält, ein Objekt, das das event.target.result Attribut enthält.

Da das Ereignis nur Attribut aus einem onsuccess Rückruf kommt, und hier request.onsuccess = grabEventAndContinueHandler, kann ich glaube, daß grabEventAndContinueHandler das Äquivalent des „Blockes“ des Codes ist, und das resultierende Ereignisobjekt „ergab“ zurück zu dem Haupt-Thread durch Setzen dieses globale Objekt.