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

3
我正在一个简单的项目中使用MySQL (mysql-co)和ASQ(asynquence),以更好地理解ES6生成器和yield函数,并且在奇怪的行为上遇到了难题。 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的相关代码在一个模型类中,所以我希望它不要与控制器合并。

我已经尝试了许多不同的方法,包括在subtest函数上使用或不使用yield。

出了什么问题?

2个回答

4
subtest1(conn, values)是一个生成器。yield一个生成器对象不会执行它的内容。也就是说,返回的生成器仍然被暂停,并且需要调用next()方法才能到达第一个yield。在Code Sample 2中没有明确或隐含地调用next(),这就是为什么conn.query(...)没有执行的原因。
那么yield* subtest1(conn, values)怎么样呢?从链接页面上可以看到:

yield*表达式遍历操作数并产生其返回的每个值。

它仍然会惰性地执行subtest
另一种解决方案是将subtest转换为常规函数,并返回conn.query(...)的结果(假设您只需要执行一个查询)。
function subtest1(conn, values) {
    return conn.query("INSERT INTO version SET ?", values);
}

yield生成器对象并不会启动其执行”这里有些棘手的措辞。它确实会启动,因为它调用函数并返回迭代器。只是它不会遍历返回的内容,而是在当前test1迭代中产生实际的迭代器对象。(虽然+1) - T.J. Crowder

3
yield subtest1(conn, values) 调用 subtest1(conn, values),该函数返回一个迭代器对象,然后将其作为该次迭代的值从 test1 中 yield 出来。它不会迭代由 subtest1 迭代器返回的值。
您可以通过在 yield 后面添加 * 来使 test1 迭代 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);
}

那么输出结果是:(Babel REPL上的实时代码)

f
{}
f done

请注意,我们根本没有看到任何“b”记录;这是因为您在调用bar时返回的迭代器对象后面看到的{}

如果我们只是在yield bar();行上添加*,将其转换为yield* bar(),输出结果会发生变化:(实时代码)

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

如果yield*的操作数是迭代器,则yield*会迭代它,并yield每个值。基本上是这样的:

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

你添加的例子非常好,很好地阐述了这个概念! - ricfaith

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接