7

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、このコンテキストで何が行われているのか完全にはわかりません。誰かが私がこれを理解するのを手伝ってもらえますか?

4

2 に答える 2

8

これは、Firefoxによって公開されたJavaScript 1.7の強力な新機能を活用する優れたコードであり、IndexedDBはFirefoxとChromeでのみサポートされているため、優れたトレードオフだと思います。

コードの最初の行は、関数からジェネレーターを作成し、testStepsそれを変数に割り当てますtestGenerator。ジェネレーターを使用している理由は、IndexedDBが純粋に非同期のAPIであるためです。非同期プログラミングとネストされたコールバックは苦痛です。ジェネレーターを使用すると、同期しているように見える非同期コードを記述できるため、この問題が緩和されます。

注:ジェネレーターの能力を活用して非同期コードを同期化する方法を知りたい場合は、次の記事をお読みください。

ジェネレーターが非同期プログラミングを耐えられるようにするためにどのように役立つかを説明するために、次のコードを検討してください。

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の記事を参照してください。お役に立てれば。

于 2012-06-15T05:33:12.583 に答える
1

これは、MozillaコードベースのIDBテストのいたるところgrabEventAndContinueHandler()に散らばっていますが、これらのいくつかを超える定義を見つけることができません。

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

関数の定義がないと、それが何をするのかはわかりませんが、それらがテストスイートの一部であると推測し、他の関数と同じようにイベントメッセージを渡す必要があります。yieldグローバルであるように見えます。おそらく、テストスイートの内部から結果を返しますgrabEventAndContinueHandler()


これは、、および呼び出しからのイベント結果でyield設定されるグローバルオブジェクトにすぎないと思います。grabEventAndContinueHandlercreateObjectStoreobjectStore.add()objectStore.get

役立つ場合はyield、Rubyでの概念の使用に関する背景を説明します。これは一種のmap()-のようなものです。これは、イテレータの外部にあるコードの「ブロック」にメッセージを返すキーワードです。

ここで何をしているのかyieldははっきりとは言えませんが(関数ではないようです)、IndexedDBに関する私の知識に基づいたショットを次に示します。

これがIDBを扱っていることを考えると、ここでのyieldオブジェクトlet event = yieldには、属性を含むオブジェクトであるイベントオブジェクト()が含まれていることがわかりevent.target.resultます。

そのイベント属性はonsuccessコールバックからのみ取得されるため、ここでは、これはコードの「ブロック」に相当し、このグローバルオブジェクトを設定することで、結果のイベントオブジェクトがメインスレッドに「生成」されるとrequest.onsuccess = grabEventAndContinueHandler推測できます。grabEventAndContinueHandler

于 2012-06-07T18:37:56.123 に答える