为什么不从`.map`callback中产生回报?

学习生成器 – 4»捕获错误! 该解决scheme使用for loop但我只是找不到任何MDN – 迭代协议 ,指的是callback内的产量。

我会猜测答案就是don't do that但是如果有人有时间或倾向提供一个解释,请提前致谢!

码:

 function *upper (items) { items.map(function (item) { try { yield item.toUpperCase() } catch (e) { yield 'null' } } } var badItems = ['a', 'B', 1, 'c'] for (var item of upper(badItems)) { console.log(item) } // want to log: A, B, null, C 

错误:

 ⇒ learn-generators run catch-error-map.js /Users/gyaresu/programming/projects/nodeschool/learn-generators/catch-error-map.js:4 yield item.toUpperCase() // error below ^^^^ SyntaxError: Unexpected identifier at exports.runInThisContext (vm.js:73:16) at Module._compile (module.js:443:25) at Object.Module._extensions..js (module.js:478:10) at Module.load (module.js:355:32) at Function.Module._load (module.js:310:12) at Function.Module.runMain (module.js:501:10) at startup (node.js:129:16) at node.js:814:3 

即使我的编辑知道这是一个可怕的想法…

回调内的收益率

免责声明:我是Learn generator workshopper的作者

通过回答@slebetman是有点正确的,我也可以添加更多:

是的, MDN – 迭代协议不直接引用callback中的yield 。 但是,它告诉我们关于从哪里yield产品的重要性,因为您只能在发电机内使用yield 。 查看MDN – Iterables文档了解更多信息。

@marocchino build议好的解决scheme迭代映射后更改的数组:

 function *upper (items) { yield* items.map(function (item) { try { return item.toUpperCase(); } catch (e) { return null; } }); } 

我们可以这样做,因为Array具有迭代机制,请参阅Array.prototype [@@ iterator]() 。

 var bad_items = ['a', 'B', 1, 'c']; for (let item of bad_items) { console.log(item); // a B 1 c } 

Array.prototype.map没有默认的迭代行为,所以我们不能迭代它。

但是生成器不仅仅是迭代器。 每个生成器都是一个迭代器,但不是相反的。 生成器允许您通过调用yield关键字来自定义迭代(而不仅仅是)过程。 你可以在这里玩和看看生成器/迭代器之间的区别:

演示 : babel / repl 。

一个问题是yield只是函数调用者的一个级别。 所以当你在callback中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' } } } 

所以在上面的代码中,你完全不能访问yield值。 Array.prototype.map确实可以访问Array.prototype.map值。 如果你是编写.map()的代码的人,你可以得到这个值。 但是既然你不是写Array.prototype.map的人,并且由于写了Array.prototype.map的人不会重新产生屈服值,所以你不能访问屈服值(s)根本(希望他们将全部收集垃圾)。

我们可以使它工作吗?

让我们看看我们是否可以在callback中做出收益率的工作。 我们可以写一个函数,像.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' } }) } 

注意你需要两次产出。 这是因为内部收益返回到mapGen那么mapGen将产生该值,那么您需要产生它,以便从upper返回该值。

好。 这种作品,但不完全是:

 var u = upper(['aaa','bbb','ccc']); console.log(u.next().value); // returns generator object 

不完全是我们想要的。 但是这样做是有道理的,因为第一次收益率就是收益率。 所以我们将每个产量作为发生器对象处理? 让我们来看看:

 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:

 function *upper (items) { yield* items.mapGen(/*...*/); } 

得到mapGen()的返回值。 现在,让我们忽略mapGen做了什么,只是想想实际yield是多less。

所以我们第一次调用.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关键字)没有(未定义)。

这就是为什么第二个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 

好极了! 但是,如果是这样的话,callback中的内在yield怎么样呢?

那么,如果你仔细想想,你会意识到callback中最内层的yield就像*upper()yield – 它只会返回一个值。 但是我们不会多次使用它。 这是因为我们第二次调用uu.next()我们没有返回相同的callbackuu.next()而是另一个callbackuu.next()而这个callback函数也将只返回一个值。

所以它的工作。 或者可以使其工作。 但它有点愚蠢。

结论:

毕竟,关于为什么yield没有按照我们预期的方式来实现的关键点是, yield暂停代码执行,并在下一行继续执行。 如果没有更多的收益,那么发电机终止(是.done )。

要意识到的第二点是,callback和所有这些数组方法( .map.forEach等)并不神奇。 他们只是JavaScript的function。 因此,把它们当作控制结构或者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); 

但是你会注意到,在这种情况下,我们返回forms的callback(而不是收益),我们也返回forms上。 所以这个例子在for循环里面回到了一个yield ,这不是我们正在讨论的。