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 或暂停生成器yield

gen.sendgrabEventAndContinueHandler函数调用方法时,生成器会自动恢复。该函数只接受一个调用的参数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只是一个全局对象,它使用来自,和调用grabEventAndContinueHandler的事件结果进行设置。createObjectStoreobjectStore.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 回答