Объясните, как генератор используется в этом коде JavaScript с IndexedDB?

Пробираясь через чудесный мир IndexedDB, я наткнулся на кодэтот из набора тестов Mozilla:

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

Их другие тесты написаны в похожем стиле, в отличие от типичной «пирамиды гибели». стиль, который вы видите с IndexedDB из-за того, что асинхронные обратные вызовы собраны вместе (и, конечно, генераторы широко не поддерживаются, кроме Firefox ...).

Таким образом, этот код от Mozilla несколько привлекателен и интригует меня, поскольку выглядит очень чисто, но я не совсем уверен, чтоyield делает в этом контексте. Может ли кто-нибудь помочь мне понять это?

 Aadit M Shah15 июн. 2012 г., 07:41
Когдаyield ключевое слово, генератор приостановлен доnext илиsend метод вызывается на нем.send Метод принимает один аргумент и возобновляет генератор, отправляя данный аргумент в генератор.next метод такой же, какsend метод, за исключением того, что он всегда отправляет значениеundefined к генератору.grabEventAndContinueHandler просто возобновляет генератор и отправляет егоevent он получил, который пойман в генераторе операторомvar event = yield;.
 buley10 июн. 2012 г., 19:02
какие детали я могу предоставить?
 dumbmatter10 июн. 2012 г., 21:06
Кроме того, спасибо за ваш оригинальный ответ и ваши другие ответы IndexedDB здесь. Вы, похоже, один из немногих людей в мире, которые пишут о том, как его следует использовать.
 dumbmatter10 июн. 2012 г., 21:05
Я не совсем уверен. Я все еще не совсем понимаю, что происходит. Для справки,here is where grabEventAndContinueHandler is defined, Это как-то говорит "когда вы добираетесь доyield подождите, пока событие не закончится? Как?

Ответы на вопрос(2)

Решение Вопроса

который использует мощные новые функции JavaScript 1.7, предоставляемые Firefox, и поскольку IndexedDB поддерживается только Firefox и Chrome, я бы сказал, что это отличный компромисс.

Первая строка кода создает генератор из функцииtestSteps и присваивает его переменнойtestGenerator, Причина, по которой мы используем генераторы, заключается в том, что IndexedDB является чисто асинхронным API; Асинхронное программирование и вложенные обратные вызовы - это боль. Использование генераторов облегчает эту проблему, позволяя писать асинхронный код, который выглядит синхронно.

Note: Если вы хотите узнать, как использовать возможности генераторов для синхронного асинхронного кода, прочитайтеследующая статья.

Чтобы объяснить, как генераторы полезны для переноса асинхронного программирования, рассмотрим следующий код:

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

В приведенном выше коде мы запросили базу данных с именемTest, Мы запросили версию базы данных1.0, Поскольку он не существует,onupgradeneeded обработчик события был уволен. Как только мы получили базу данных, мы создали хранилище объектов, добавили объект в хранилище объектов, и после ее сохранения мы закрыли базу данных.

Проблема с приведенным выше кодом заключается в том, что мы запрашиваем базу данных и выполняем другие операции, связанные с ней асинхронно. Это может сделать код очень сложным для обслуживания, поскольку используется все больше и больше вложенных обратных вызовов.

Для решения этой проблемы мы используем генераторы следующим образом:

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

grabEventAndContinueHandler Функция определяется после генератора следующим образом:

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

Генератор запускается следующим образом:

gen.next();

После запуска генератора делается запрос на установление соединения с данной базой данных. затемgrabEventAndContinueHandler прикреплен как обработчик события кonupgradeneeded событие. Наконец, мы выдаем или приостанавливаем генератор, используя ключевое словоyield.

Генератор автоматически возобновляется, когдаgen.send метод вызывается изgrabEventAndContinueHandler функция. Эта функция просто принимает один аргументevent и отправляет его генератору. Когда генератор возобновляется, отправленное значение сохраняется в переменной с именемevent.

Напомним, магия происходит здесь:

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

Приведенный выше код позволяет писать асинхронный код, как если бы он был синхронным. Чтобы узнать больше о генераторах, прочитайте следующееСтатья MDN, Надеюсь это поможет.

 15 июл. 2012 г., 21:37
Отличное объяснение! Одна вещь, на которую следует обратить внимание, это то, что "yield" не действительно "останавливается" или "пауза" выполнение. Он просто возвращается к вызову gen.send () или gen.next (). Самое интересное в том, что вы продолжаете выполнение, вызывая gen.send () или gen.next () снова. Еще круче то, что yield работает внутри операторов if и конструкций цикла. Это облегчает написание цикла, который асинхронно зацикливается на курсоре. Однако следует обратить внимание на то, что синтаксис, который использует Firefox, на 100% не будет соответствовать синтаксису, который стандартизирует ES6. Мы, конечно, будем обновляться в соответствии со спецификацией.

grabEventAndContinueHandler() замусоренповсюду место в тестах IDB в кодовой базе Mozilla, но я не могу найти определение за пределами парыэти:

  testGenerator.send(event);
} 

Без определения функции я не могу сказать, что она делает, но мне нужно угадать, что они являются частью набора тестов, и передавать сообщения о событиях, как это делают другие.yield представляется глобальным, возможно, который передает результаты из набора тестов изнутри егоgrabEventAndContinueHandler().

Я бы догадался, чтоyield вот только глобальный объект, который устанавливается вgrabEventAndContinueHandler с результатом события изcreateObjectStore, objectStore.add() а такжеobjectStore.get вызовы.

В случае, если это полезно, я дам вам некоторые сведения об использованииyield Концепция в Ruby. Это вроде какmap() - это ключевое слово, которое передает сообщения обратно в «блок». кода вне итератора.

Я не могу сказать, чтоyield делать здесь с уверенностью (это не похоже на функцию), но вот пример, основанный на моих знаниях IndexedDB.

Учитывая, что это касается IDB, я знаю, что объект yield здесь содержит объект события (let event = yield), объект, который содержитevent.target.result приписывать.

Поскольку этот атрибут события происходит только отonsuccess обратный звонок, а здесьrequest.onsuccess = grabEventAndContinueHandlerМогу догадаться чтоgrabEventAndContinueHandler является эквивалентом «блока» кода и результирующий объект события «получен»; вернуться к основному потоку, установив этот глобальный объект.

Ваш ответ на вопрос