Learn Generators - 4 » CATCH ERROR!
该解决方案使用for loop
,但我在MDN - Iteration Protocols中找不到任何引用回调中的收益的内容。
我猜这个答案只是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
甚至我的编辑都知道这是一个糟糕的想法......
答案 0 :(得分:16)
免责声明:我是Learn generators研讨会的作者。
@slebetman的答案是正确的,我也可以添加更多:
是的,MDN - Iteration Protocol没有直接引用回调中的yield
。
但是,它告诉我们yield
项目的重要性,因为您只能在生成器中使用yield
。请参阅MDN - Iterables文档以了解详情。
@marocchino suggest只需很好的解决方案迭代地图后更改的数组:
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。
答案 1 :(得分:14)
一个问题是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
的人不会重新产生所产生的价值,因此您无法获得所产生的价值。完全(希望他们将全部垃圾收集)。
让我们看看我们是否可以在回调中使收益率发挥作用。我们可以为生成器编写一个行为类似于.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 *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关键字而没有产生)没有(未定义)。
这就是第二个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
的行为与yield
中的*upper()
一样 - 它只会返回一个值。但我们从不使用过一次。那是因为我们第二次调用uu.next()
时我们没有返回相同的回调,而是另一个回调,而这反过来也只返回一个值。
所以它有效。或者它可以工作。但这有点愚蠢。
毕竟,关键是要了解yield
为什么不能按照我们的预期运行,yield
暂停代码执行并在下一行继续执行。如果没有更多的收益,则生成器终止(.done
)。
要意识到的第二点是回调和所有那些数组方法(.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
,这不是我们正在讨论的内容。
答案 2 :(得分:0)
您可以通过“ co-npm”使用另一种方法:co.wrap(fn *)
function doSomething(){
return new promise()
}
var fn = co.wrap(function* (arr) {
var data = yield arr.map((val) => {
return doSomething();
});
return data;
});
fn(arr).then(function (val) {
consloe.log(val)
});