一个问题是yield 只为函数的调用者产生一个级别。因此,当您在回调中 yield 时,它可能不会像您认为的那样做:
// The following yield:
function *upper (items) { // <---- does not yield here
items.map(function (item) { // <----- instead it yields here
try {
yield item.toUpperCase()
} catch (e) {
yield 'null'
}
}
}
所以在上面的代码中,您绝对无法访问产生的值。 Array.prototype.map 确实可以访问产生的值。如果您是为.map() 编写代码的人,您可以获得该值。但是由于您不是写Array.prototype.map 的人,并且由于写Array.prototype.map 的人没有重新产生产生的值,所以您根本无法访问产生的值(并且希望它们都被垃圾收集)。
我们可以让它工作吗?
让我们看看是否可以让 yield 在回调中起作用。我们或许可以为生成器编写一个行为类似于.map() 的函数:
// WARNING: UNTESTED!
function *mapGen (arr,callback) {
for (var i=0; i<arr.length; i++) {
yield callback(arr[i])
}
}
那么你可以这样使用它:
mapGen(items,function (item) {
yield item.toUpperCase();
});
或者如果你够勇敢,你可以扩展Array.prototype:
// WARNING: UNTESTED!
Array.prototype.mapGen = function *mapGen (callback) {
for (var i=0; i<this.length; i++) {
yield callback(this[i])
}
};
我们大概可以这样称呼它:
function *upper (items) {
yield* items.mapGen(function * (item) {
try {
yield item.toUpperCase()
} catch (e) {
yield 'null'
}
})
}
请注意,您需要让步两次。这是因为内部 yield 返回到 mapGen 然后 mapGen 将产生该值,然后您需要 yield 它才能从 upper 返回该值。
好的。这种工作但不完全:
var u = upper(['aaa','bbb','ccc']);
console.log(u.next().value); // returns generator object
不完全是我们想要的。但这有点道理,因为第一个收益率会返回一个收益率。所以我们把每个yield当作一个生成器对象来处理?让我们看看:
var u = upper(['aaa','bbb','ccc']);
console.log(u.next().value.next().value.next().value); // works
console.log(u.next().value.next().value.next().value); // doesn't work
好的。让我们弄清楚为什么第二次调用不起作用。
上层函数:
function *upper (items) {
yield* items.mapGen(/*...*/);
}
产生mapGen() 的返回值。现在,让我们先忽略mapGen 的作用,而只考虑yield 的实际含义。
所以我们第一次调用.next()时函数在这里暂停:
function *upper (items) {
yield* items.mapGen(/*...*/); // <----- yields value and paused
}
这是第一个console.log()。第二次调用.next(),函数调用在yield之后的行继续:
function *upper (items) {
yield* items.mapGen(/*...*/);
// <----- function call resumes here
}
返回(不是 yield,因为该行没有 yield 关键字)什么都没有(未定义)。
这就是第二个console.log() 失败的原因:*upper() 函数已经用完了要让出的对象。事实上,它只产生一次,所以它只有一个要产生的对象——它是一个只产生一个值的生成器。
好的。所以我们可以这样做:
var u = upper(['aaa','bbb','ccc']);
var uu = u.next().value; // the only value that upper will ever return
console.log(uu.next().value.next().value); // works
console.log(uu.next().value.next().value); // works
console.log(uu.next().value.next().value); // works
耶!但是,如果是这样的话,回调中最里面的yield怎么会起作用呢?
好吧,如果您仔细考虑,您会发现回调中最里面的yield 的行为也类似于*upper() 中的yield - 它只会返回一个值。但我们从不多次使用它。这是因为我们第二次调用 uu.next() 时,我们返回的不是同一个回调,而是另一个回调,而另一个回调也只会返回一个值。
所以它起作用了。或者它可以工作。但这有点愚蠢。
结论:
毕竟,要了解为什么yield 不能按我们预期的方式工作的关键是yield 暂停代码执行并在下一行继续执行。如果没有更多的收益,则生成器终止(.done)。
要意识到的第二点是回调和所有这些 Array 方法(.map、.forEach 等)并不神奇。它们只是 javascript 函数。因此,将它们视为for 或while 之类的控制结构有点错误。
结语
有一种方法可以让mapGen 干净地工作:
function upper (items) {
return items.mapGen(function (item) {
try {
return item.toUpperCase()
} catch (e) {
return 'null'
}
})
}
var u = upper(['aaa','bbb','ccc']);
console.log(u.next().value);
console.log(u.next().value);
console.log(u.next().value);
但是您会注意到,在这种情况下,我们从回调(不是 yield)返回,而且我们还从 upper 返回。所以这个案例又回到了 for 循环中的yield,这不是我们要讨论的。