2012-06-07 6 views
6

Pur facendo la mia strada attraverso il meraviglioso mondo del IndexedDB, mi sono imbattuto in codice come this dalla suite di test di Mozilla:Spiega come viene utilizzato un generatore in questo codice JavaScript con 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; 
} 

I loro altri test sono scritti in uno stile simile, in contrasto con la piramide tipica" di sventura "lo stile che vedi con IndexedDB a causa del fatto che i callback asincroni vengono raggruppati insieme (e, ovviamente, i generatori non sono ampiamente supportati oltre a Firefox ..).

Quindi, questo codice di Mozilla è un po 'attraente e intrigante per me perché sembra molto pulito, ma non sono del tutto sicuro di cosa stia facendo yield in questo contesto. Qualcuno può aiutarmi a capire questo?

+0

che tipo di dettagli posso fornire? – buley

+0

Non ne sono completamente sicuro. Ancora non capisco cosa sta succedendo. Per riferimento, [qui è dove è definita grabEventAndContinueHandler] (http://hg.mozilla.org/mozilla-central/file/895e12563245/dom/indexedDB/test/helpers.js). In qualche modo sta dicendo "quando arrivi alla linea' yield', aspetti che l'evento sia finito "? Come? – dumbmatter

+0

Inoltre, grazie per la risposta originale e le altre risposte IndexedDB qui. Sei una delle poche persone al mondo a scrivere su come dovrebbe essere usato, a quanto pare. – dumbmatter

risposta

4

Questo è un pezzo di codice brillante che sfrutta le nuove potenti funzionalità di JavaScript 1.7 esposte da Firefox, e poiché IndexedDB è supportato solo da Firefox e Chrome, direi che è un ottimo compromesso.

La prima riga del codice crea un generatore dalla funzione testSteps e lo assegna alla variabile testGenerator. Il motivo per cui utilizziamo i generatori è perché IndexedDB è un'API puramente asincrona; e la programmazione asincrona e le callback annidate sono un problema. L'uso dei generatori facilita questo dolore consentendo di scrivere codice asincrono che sembra sincrono.

Nota: Se vuoi sapere come sfruttare la potenza dei generatori per rendere il codice asincrono sincrono leggere il following article.

per spiegare come i generatori sono utili per rendere la programmazione asincrona sopportabile consideri il seguente codice:

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

Nel codice precedente abbiamo richiesto per un database denominato Test. Abbiamo richiesto la versione del database 1.0. Dal momento che non esisteva il gestore di eventi onupgradeneeded è stato licenziato. Una volta ottenuto il database, abbiamo creato un archivio oggetti su di esso, aggiunto un oggetto all'archivio oggetti e, dopo che è stato salvato, abbiamo chiuso il database.

Il problema con il codice precedente è che stiamo richiedendo il database e eseguendo altre operazioni correlate in modo asincrono. Ciò potrebbe rendere il codice molto difficile da gestire in quanto vengono utilizzati callback sempre più nidificati.

Per risolvere questo problema usiamo generatori come segue:

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

La funzione grabEventAndContinueHandler viene definita dopo il generatore come segue:

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

Il generatore viene avviato come segue:

gen.next(); 

Una volta avviato il generatore viene richiesta di aprire ac onnection al database specificato. Quindi grabEventAndContinueHandler viene collegato come gestore eventi all'evento onupgradeneeded. Alla fine cediamo o sospendiamo il generatore usando la parola chiave yield.

Il generatore viene ripreso automaticamente quando viene chiamato il metodo dalla funzione grabEventAndContinueHandler. Questa funzione prende semplicemente un singolo argomento chiamato event e lo invia al generatore. Al ripristino del generatore, il valore inviato viene memorizzato in una variabile denominata event.

Per ricapitolare, la magia accade qui:

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

Il codice precedente consente di scrivere codice asincrono come se fosse sincrona. Per saperne di più sui generatori leggi il seguente MDN article. Spero che questo ti aiuti.

+1

Ottima spiegazione! Una cosa da sottolineare è che "yield" in realtà non "ferma" o "sospende" l'esecuzione. Ritorna semplicemente alla chiamata gen.send() o gen.next(). La parte interessante è naturalmente che si continua l'esecuzione chiamando gen.send() o gen.next() di nuovo. Ancora più interessante è che la resa funziona all'interno di istruzioni if ​​e costrutti di loop. Ciò semplifica la scrittura di un ciclo che scorre in modo asincrono su un cursore. Una cosa da tenere a mente è che la sintassi utilizzata da Firefox probabilmente non corrisponderà al 100% alla sintassi che ES6 standardizzerà. Naturalmente aggiorneremo per seguire le specifiche. –

1

Il grabEventAndContinueHandler() è disseminato all overthe place nelle prove IDB del codice di Mozilla, ma non riesco a trovare una definizione al di là di un paio di these:

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

Senza una definizione di funzione non posso dire che cosa lo fa ma dovrei indovinare che fanno parte della suite di test e passare messaggi di eventi come fanno questi altri. yield sembra essere un globale, che forse restituisce i risultati della suite di test dal suo grabEventAndContinueHandler().


direi che yield qui è solo un oggetto globale che viene impostato in grabEventAndContinueHandler con il risultato evento dalla createObjectStore, objectStore.add() e objectStore.get invocazioni.

Nel caso sia utile, ti darò qualche informazione sull'uso del concetto yield in Ruby. È una specie di map() - è una parola chiave che rimanda i messaggi a un "blocco" di codice al di fuori dell'iteratore.

Non posso dire quello che yield facendo qui con certezza (non sembra essere una funzione), ma ecco un colpo in base alla mia conoscenza della IndexedDB.

Dato questo si occupa di IDB, so che l'oggetto resa qui contiene l'oggetto evento (let event = yield), un oggetto che contiene l'attributo event.target.result.

Da quell'evento attributo viene solo da un onsuccess callback e qui request.onsuccess = grabEventAndContinueHandler, posso immagino che grabEventAndContinueHandler è l'equivalente di "blocco" del codice e l'oggetto evento risultante viene "ceduto" ritorno al filo principale modificando questo oggetto globale.