2012-06-07 13 views
6

Si bien haciendo mi camino a través del maravilloso mundo de IndexedDB, me encontré con un código como this del conjunto de pruebas de Mozilla:¿Explica cómo se usa un generador en este código 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; 
} 

Sus otras pruebas están escritos en un estilo similar, a diferencia de la típica pirámide" de la condenación "Estilo que se ve con IndexedDB debido a que las devoluciones de llamada asincrónicas se apilan juntas (y, por supuesto, los generadores no son ampliamente compatibles más allá de Firefox ...).

Por lo tanto, este código de Mozilla es algo atractivo y intrigante para mí, ya que se ve muy limpio, pero no estoy totalmente seguro de lo que yield está haciendo en este contexto. ¿Alguien puede ayudarme a entender esto?

+0

¿qué tipo de detalle puedo ofrecer? – buley

+0

No estoy del todo seguro. Todavía no entiendo realmente lo que está pasando. Como referencia, [aquí es donde se define grabEventAndContinueHandler] (http://hg.mozilla.org/mozilla-central/file/895e12563245/dom/indexedDB/test/helpers.js). ¿De alguna manera está diciendo "cuando llegues a la línea' yield', esperas hasta que el evento haya terminado? ¿Cómo? – dumbmatter

+0

Además, gracias por su respuesta original y sus otras respuestas IndexedDB aquí. Según parece, eres una de las pocas personas en el mundo que realmente escribe sobre cómo debería usarse. – dumbmatter

Respuesta

4

Esta es una pieza brillante de código que aprovecha las poderosas nuevas características de JavaScript 1.7 expuestas por Firefox, y dado que IndexedDB solo es compatible con Firefox y Chrome, diría que es una excelente compensación.

La primera línea del código crea un generador a partir de la función testSteps y lo asigna a la variable testGenerator. La razón por la que estamos usando generadores es porque IndexedDB es una API puramente asíncrona; y la programación asincrónica y las devoluciones de llamada anidadas son un problema. El uso de generadores alivia este dolor al permitirle escribir código asíncrono que se ve sincrónico.

Nota: Si desea saber cómo aprovechar la potencia de los generadores para hacer que el código asíncrono sea sincrónico, lea following article.

para explicar cómo los generadores son útiles para hacer la programación asincrónica soportable considere el siguiente código:

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

En el código anterior que habíamos pedido para una base de datos llamada Test. Solicitamos la versión de la base de datos 1.0. Como no existía, se desencadenó el controlador de eventos onupgradeneeded. Una vez que obtuvimos la base de datos, creamos un almacén de objetos en ella, añadimos un objeto al almacén de objetos y, después de guardarla, cerramos la base de datos.

El problema con el código anterior es que estamos solicitando la base de datos y realizando otras operaciones relacionadas de forma asincrónica. Esto podría hacer que el código sea muy difícil de mantener a medida que se emplean más y más callbacks anidados.

Para resolver este problema utilizamos generadores de la siguiente manera:

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 función grabEventAndContinueHandler se produce después de que el generador de la siguiente manera:

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

El generador se inicia de la siguiente manera:

gen.next(); 

Una vez que se inicia el generador, se solicita abrir CA. conexión a la base de datos dada. A continuación, se conecta grabEventAndContinueHandler como controlador de eventos al evento onupgradeneeded. Finalmente cedemos o pausamos el generador usando la palabra clave yield.

El generador se reanuda automáticamente cuando se llama al método gen.send desde la función grabEventAndContinueHandler. Esta función simplemente toma un único argumento llamado event y lo envía al generador. Cuando se reanuda el generador, el valor enviado se almacena en una variable llamada event.

En resumen, la magia sucede aquí:

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

El código anterior hace que sea posible escribir código asíncrono como si fuera síncrono. Para saber más sobre generadores, lea el siguiente MDN article. Espero que esto ayude.

+1

¡Gran explicación! Una cosa para señalar es que "rendimiento" realmente no "detiene" o "pausa" la ejecución. Simplemente vuelve a la llamada gen.send() o gen.next(). La parte positiva es, por supuesto, que continúes la ejecución al llamar a gen.send() o gen.next() nuevamente. Incluso más fresco es que el rendimiento funciona dentro de sentencias if y construcciones de bucle. Eso hace que sea fácil escribir un bucle que se repite de manera asincrónica sobre un cursor. Una cosa a tener en cuenta es que la sintaxis que usa Firefox probablemente no coincidirá al 100% con la sintaxis que ES6 estandarizará. Por supuesto, actualizaremos para seguir las especificaciones. –

1

El grabEventAndContinueHandler() está plagada all overthe place en las pruebas del BID en el código base de Mozilla, pero no puedo encontrar una definición más allá de un par de these:

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

Sin una definición de función que no puedo decir lo lo hace, pero tendría que adivinar que son parte del paquete de pruebas y pasar mensajes de eventos como estos otros. yield parece ser un global, tal vez que pasa los resultados desde el banco de pruebas desde adentro de su grabEventAndContinueHandler().


Me imagino que yield aquí es sólo un objeto global que consigue el sistema en grabEventAndContinueHandler con el resultado de eventos desde los createObjectStore, objectStore.add() y objectStore.get invocaciones.

En caso de que sea útil, le daré algunos antecedentes sobre el uso del concepto yield en Ruby. Es una especie de map() - es una palabra clave que pasa los mensajes a un "bloque" de código fuera del iterador.

No puedo decir qué yield está haciendo aquí con certeza (no parece ser una función), pero esta es una foto basada en mi conocimiento de IndexedDB.

Dado que se trata de IDB, sé que el objeto de rendimiento aquí contiene el objeto de evento (let event = yield), un objeto que contiene el atributo event.target.result.

Desde ese evento atributo de sólo proviene de una devolución de llamada onsuccess, y aquí request.onsuccess = grabEventAndContinueHandler, puedo adivinar que grabEventAndContinueHandler es el equivalente del "bloque" de código y el objeto de evento resultante se "dio" vuelta a la rosca principal, fijando este objeto global.

Cuestiones relacionadas