为什么不从`.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
,这不是我们正在讨论的。