Javascript 奇怪的生成器 yield 子函数行为

Javascript strange generator yield sub function behavior

我在一个简单的项目中使用 MySQL (mysql-co) 和 ASQ(asynquence) 来更好地理解 ES6 生成器和 yield 函数,我是难倒了一个奇怪的行为。

asynquence

的简短说明

asynquence (https://github.com/getify/asynquence) 为我提供了一种按顺序 运行 生成器的简单方法。它还可以进行伪并行执行,但这不是我现在需要的。 function *x(token) 的结构来自那里。 token[0] 持有一个连接对象。 yield token 按顺序将控制权交给下一个生成器函数。

代码示例 1(有效)

function *test1(token) {
  var conn = token.messages[0];
  var values = {id:1, dev:1, description:'This is it!'};
  yield conn.query("INSERT INTO version SET ?", values);
  yield token;
}

这很好用。插入上述行。我不知道 MySQL 驱动程序允许如此简单的插入功能,但它确实如此。

代码示例 2(不起作用)

function *test1(token) {
  var conn = token.messages[0];
  var values = {id:1, dev:1, description:'This is it!'};
  yield subtest1(conn, values);
  yield token;
}
function *subtest1(conn, values) {
  yield conn.query("INSERT INTO version SET ?", values);
}

这行不通。 subtest1 的实际代码在模型 class 中,所以我不希望将它与控制器合并。

我在子测试函数上尝试了很多不同的方法,有或没有 yield。

怎么回事?

subtest1(conn, values) 是一个生成器。 yield生成器对象不会执行其主体。也就是说,生成的生成器保持挂起状态,并且需要调用 next() 方法才能到达第一个 yield代码示例 2 中没有对 next() 的显式或隐式调用,这就是未执行 conn.query(...) 的原因。

yield* subtest1(conn, values)怎么样?来自链接页面:

The yield* expression iterates over the operand and yields each value returned by it.

它仍然会延迟执行subtest

另一种解决方案是将 subtest 变成常规函数,并将 return 变成 conn.query(...) 的结果(假设您只需要执行一个查询):

function subtest1(conn, values) {
    return conn.query("INSERT INTO version SET ?", values);
}

yield subtest1(conn, values) 调用 subtest1(conn, values),其中 returns 是一个迭代器对象,然后从 test1 中生成它作为 test1 迭代的值。它不会迭代 subtest1 迭代器返回的值。

可以test1通过在yield之后添加*来迭代subtest1的迭代器的值:

yield* subtest1(conn, values);

但看看你的代码,我认为你不想这样做。如果你想将 conn.query 行拆分成一个函数,它看起来像这样:

function *test1(token) {
  var conn = token.messages[0];
  var values = {id:1, dev:1, description:'This is it!'};
  yield subtest1(conn, values);
  yield token;
}
function subtest1(conn, values) {
  return conn.query("INSERT INTO version SET ?", values);
}

这个更简单的版本可能有助于区分 yieldyield*:

function* foo() {
  console.log("f");
  yield bar();
  console.log("f done");
}
function* bar() {
  console.log("b1");
  let i = 0;
  while (i < 3) {
    console.log("b2");
    yield i;
    ++i;
  }
  console.log("b done");
}
for (let v of foo()) {
  console.log(v);
}

其输出为:(live copy on Babel's REPL)

f
{}
f done

请注意我们根本看不到任何 "b" 日志记录;那是因为你在 f1 之后看到的 {} 是通过调用 bar.

返回的迭代器对象

如果我们只是在 yield bar(); 行上添加 *,将其变成 yield* bar(),输出会发生变化:(live copy)

f
b1
b2
0
b2
1
b2
2
b done
f done

如果 yield* 的操作数是一个迭代器,yield* 对其进行迭代,yield 计算它的每个值。基本上是:

for (value of bar()) {
    yield value;
}