2012-06-07 6 views
6

Robiąc moją drogę przez wspaniałego świata IndexedDB, natknąłem się na kodzie jak this z Mozilli testowego pakietu:Wyjaśnij, w jaki sposób generator jest używany w tym kodzie JavaScript z IndexedDB?

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

Ich inne testy są pisane w podobnym stylu, w przeciwieństwie do typowych „piramidy zagłady "styl, który widzisz w IndexedDB z powodu asynchronicznych wywołań zwrotnych, które są ułożone razem (i, oczywiście, generatory nie są szeroko obsługiwane poza Firefoksem ...).

Ten kod z Mozilli jest dla mnie nieco pociągający i intrygujący, ponieważ wygląda bardzo czysto, ale nie jestem całkowicie pewien, co robi w tym kontekście yield. Czy ktoś może mi pomóc to zrozumieć?

+0

jaki szczegół mogę dostarczyć? – buley

+0

Nie jestem do końca pewien. Wciąż nie bardzo rozumiem, co się dzieje. Dla odniesienia, [tutaj jest zdefiniowane pojęcie grabEventAndContinueHandler] (http://hg.mozilla.org/mozilla-central/file/895e12563245/dom/indexedDB/test/helpers.js). Czy to w jakiś sposób mówi "kiedy dojdziesz do linii' yield', poczekaj, aż zdarzenie się zakończy "? W jaki sposób? – dumbmatter

+0

Dziękuję również za odpowiedź oryginalną i inne odpowiedzi IndexedDB tutaj. Jesteś jednym z niewielu ludzi na świecie, którzy piszą o tym, jak powinno się to robić. – dumbmatter

Odpowiedz

4

To genialny kawałek kodu, który wykorzystuje nowe, potężne funkcje JavaScript 1.7 odsłonięte przez Firefoksa, a ponieważ IndexedDB jest obsługiwany tylko przez Firefoksa i Chrome, powiedziałbym, że jest to doskonały kompromis.

Pierwsza linia kodu tworzy generator z funkcji testSteps i przypisuje go do zmiennej testGenerator. Powodem, dla którego używamy generatorów jest to, że IndexedDB jest czysto asynchronicznym API; a programowanie asynchroniczne i zagnieżdżone wywołania zwrotne są uciążliwe. Korzystanie z generatorów łagodzi ten ból, umożliwiając zapisanie asynchronicznego kodu, który wygląda synchronicznie.

Uwaga: Jeśli chcesz wiedzieć, jak wykorzystać moc generatorów, aby kod asynchroniczny synchroniczny przeczytać following article.

Aby wyjaśnić, jak generatory są przydatne do programowania asynchronicznego znośne rozważyć następujący kod:

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(); 
    }; 
}; 

w powyższym kodzie poprosiliśmy dla bazy danych o nazwie Test. Poprosiliśmy o wersję bazy danych 1.0. Ponieważ nie istniało, program obsługi zdarzeń onupgradeneeded został uruchomiony. Po uzyskaniu bazy danych utworzyliśmy na niej składnicę obiektów, dodaliśmy obiekt do składnicy obiektów, a po zapisaniu zamknęliśmy bazę danych.

Problem z powyższym kodem polega na tym, że prosimy o bazę danych i wykonujemy inne operacje związane z nią asynchronicznie. Może to utrudnić utrzymanie kodu, ponieważ wykorzystuje się coraz więcej zagnieżdżonych wywołań zwrotnych.

Aby rozwiązać ten problem używamy generatorów następująco:

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

Funkcja grabEventAndContinueHandler jest zdefiniowana po generatora następująco:

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

Generator jest uruchamiany w następujący sposób:

gen.next(); 

Po uruchomieniu generatora zostaje wyświetlone żądanie otwarcia przywiązanie do podanej bazy danych. Następnie grabEventAndContinueHandler jest dołączony jako event event do zdarzenia onupgradeneeded. W końcu dajemy lub wstrzymujemy generator za pomocą słowa kluczowego yield.

Generator zostanie automatycznie wznowiony po wywołaniu metody gen.send z funkcji grabEventAndContinueHandler. Ta funkcja pobiera pojedynczy argument o nazwie event i wysyła go do generatora. Po wznowieniu pracy generatora wysłana wartość zostaje zapisana w zmiennej o nazwie event.

Reasumując, magia dzieje się tutaj:

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

Powyższy kod umożliwia pisanie kodu asynchronicznego, jak gdyby były synchroniczne. Aby dowiedzieć się więcej o generatorach, przeczytaj następującą instrukcję: MDN article. Mam nadzieję że to pomoże.

+1

Świetne wyjaśnienie! Należy zwrócić uwagę na to, że "wydajność" tak naprawdę nie "zatrzymuje się" ani "pauzuje" wykonania. Po prostu wraca do wywołania gen.send() lub gen.next(). Najfajniejszą częścią jest oczywiście kontynuowanie wykonywania przez ponowne wywołanie funkcji gen.send() lub gen.next(). Jeszcze chłodniej, że plon działa wewnątrz instrukcji if i konstrukcji pętli. Dzięki temu łatwo jest napisać pętlę, która pętle asynchronicznie nad kursorem. Jedną z rzeczy, na które należy zwrócić uwagę, jest to, że składnia używana przez Firefoksa prawdopodobnie nie będzie w 100% zgodna ze składnią standardową dla standardu ES6. Będziemy oczywiście aktualizować, aby postępować zgodnie ze specyfikacją. –

1

grabEventAndContinueHandler() jest zaśmiecona all overthe place w IDB testów w kodzie Mozilli, ale nie mogę znaleźć definicję poza kilkoma these:

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

Bez definicji funkcji nie mogę powiedzieć, co robi to, ale musiałem zgadywać, że są częścią zestawu testów i przekazują komunikaty o zdarzeniach, tak jak te inne. yield wydaje się być globalnym, być może, który przekazuje wyniki z zestawu testów z jego grabEventAndContinueHandler().


Przypuszczam, że yield tutaj jest tylko globalny obiekt, który zostanie ustawiony w grabEventAndContinueHandler z wyniku zdarzeń z createObjectStore, objectStore.add() i objectStore.get inwokacji.

Jeśli jest to pomocne, dam ci trochę informacji na temat korzystania z koncepcji yield w Ruby. Jest to podobne do map() - jest to słowo kluczowe, które przekazuje wiadomości z powrotem do "bloku" kodu poza iteratorem.

Nie mogę powiedzieć, co robi tutaj yield z pewnością (nie wydaje się być funkcją), ale tutaj jest ujęcie oparte na mojej wiedzy o IndexedDB.

Biorąc pod uwagę tę ofertę z IDB, wiem, że obiekt yield zawiera obiekt zdarzenia (let event = yield), obiekt, który zawiera atrybut event.target.result.

Ponieważ zdarzenie atrybut pochodzi wyłącznie z onsuccess zwrotnego i tu request.onsuccess = grabEventAndContinueHandler można to chyba grabEventAndContinueHandler jest równoznaczne z „bloku” kodu i wynikowy zdarzenie „uzyskane” z powrotem do głównego wątku poprzez ustawienie ten globalny obiekt.

Powiązane problemy