在生成器中使用的迭代器回调函数产生了yield。

24

有人尝试过将Underscore JS或lodash(或任何ES5标准函数)与生成器一起使用吗?

如果我们有一个数组var myArray = [1,2,3,4,6];,我们想要对它进行forEach操作。

在非生成器情况下,你可以简单地:

myArray.forEach(function(k) {
  console.log(k);
});

然而,在非生成器函数内部,你无法使用yield,所以如果在此循环中必须执行一些异步工作,则需要执行以下操作。

var foreach = function* (arr, fn) {
  var i;
  for (i = 0; i < arr.length; i++) {
    yield * fn(arr[i], i);
  }
};

yield* foreach(myArray, function* (k) {
  var a = yield fs.readFile();
});

有点糟糕。

有人知道如何使匿名函数与生成器一起工作吗?因为这个问题,我们失去了整个lodash库。

注:我正在使用Traceur将我的代码编译成启用生成器的ES6。
注:我没有使用co()。我正在使用下面看到的自定义生成器函数。

var run = function(generatorFunction) {
  var generatorItr = generatorFunction(resume);
  function resume(callbackValue) {
    generatorItr.next(callbackValue);
  }
  generatorItr.next();
};

2
可能只是我自己的问题,但我不太明白具体问题在哪里。听起来更像是在使用生成器(generators)时出现了问题,例如使用 forEach - Felix Kling
1
是的,但这并不是真正的问题。问题在于在非生成器函数内部使用yield。ForEach将90%的时间使用它。更不用说_.find(),_.filter(),Array.reduce(),Array.forEach(),Array.map()。如果您需要在其中任何一个函数中使用yield,则所有这些函数都无用。 - Sean Clark
4
对于forEach,您可以使用for (var e of arr) { yield doSomethingWith(e); }或普通的for循环。对于其他方法如filterreduce,我不认为使用生成器会有任何用处。filter回调函数必须返回布尔值。在这种情况下,使用生成器会有什么意义呢? - Felix Kling
任何我能想到的答案都是低效的代码。在循环内部执行工作。但无论如何,如果您有一个MP3 URL列表,并且需要将该列表过滤为实际存在于文件系统上的URL。通常会在列表上进行过滤,在每次迭代中检查FS,并在所有操作完成时使用Promise.all()。使用生成器,我们无法使用filter。我们必须循环并存储第二个结果数组。 - Sean Clark
@SeanClark:请注意,Promise.all 将会并行地启动 fs 查询,而你似乎正在寻找的生成器解决方案将是顺序的。 - Bergi
这是正确的,但是您可以稍后进行yield操作,使它们再次并行。您可以将fs查询推入数组并yield该数组。类似于promises,但不需要创建promises并解决它们,然后在另一个块中使用Promise.all。 - Sean Clark
1个回答

1
如果我正确理解您的问题,那么您的问题基本上是您正在尝试以异步方式执行某些操作(迭代直到找到一个好的停止点),而使用的语言(JS)实际上是围绕同步性设计的。换句话说,虽然您通常可以执行以下操作:
_([1,2,3]).any(function(x) {
    var shouldWeStopLooping = x % 2 == 0;
    return shouldWeStopLogging;
});

你希望使“我们应该停止循环”的代码从正常执行中断,然后再回来,这在传统的JS中不可能(yield相对较新于该语言),因此在Underscore/Lodash中也不可能实现。
_([1,2,3]).any(function(x) {
    var shouldWeStopLooping = $.ajax(...); // Doesn't work; code keeps going
    return shouldWeStopLogging;
});

有两种方法可供选择,但都不是理想的。
如评论中所述,一种方法是先完成所有“延迟”工作,然后进行迭代:
var workInProgress = _([1,2,3]).map(someAjaxOperation);
$.when.apply(workInProgress).done(doSomethingBasedOnAjaxResults);

但是(正如评论中所指出的),这并不完全相同,因为您最终会在数组的所有元素上执行AJAX工作(与真正的生成器不同,后者仅迭代所需数量以查找“获胜者”)。

另一种方法是消除异步性。jQuery允许您向AJAX请求传递async:false,这样可以通过使用Underscore / Lodash / whatever来“解决”问题...但它还会锁定用户的浏览器,直到完成AJAX工作,这可能不是您想要的。

不幸的是,如果您想使用类似Underscore / Lodash的库,我只能看到这些选项。您唯一的其他选择是编写自己的Underscore / Lodash混合,这真的不难。我建议这样做,因为它将允许您仍然利用这些库中的所有其他优秀功能,同时以一致的方式迭代。


不完全是。我不需要在找到x时停止循环。我只想使用lodash循环功能,例如filter和reduce。或者ES6 reduce。有些情况下,我希望在循环中发生的事情可以是异步的,但我希望以更好的方式编写该代码。这就是整个事情的重点,编写更好的代码。 - Sean Clark
此外,我同意你的jQuery async: false是客户端的解决方案,但在这里并不适用。因为这是NodeJS,所以我希望事情是异步的。我只是希望我编写的代码能够避免回调地狱。我想要一个好的解决方案来使用循环,但现在看起来yield不能在闭包情况下使用,它必须在gen函数内部。ES7似乎有异步函数-这可能是答案。 - Sean Clark

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