javascript 为什么不会从`.map` 回调中产生收益?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/30498103/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why won't yield return from within a `.map` callback?
提问by Gareth
Learn Generators - 4 ? CATCH ERROR!The solution uses a for loop
but I just couldn't find anything in MDN - Iteration Protocolsthat refers to yield within callbacks.
学习发电机 - 4 ? 捕捉错误!该解决方案使用了一个,for loop
但我在MDN - Iteration Protocols中找不到任何引用回调中的 yield 的内容。
I'm going to guess the answer is just don't do that
but thanks in advance if anyone has the time or inclination to provide an explanation!
我猜答案只是,don't do that
但如果有人有时间或愿意提供解释,请提前致谢!
Code:
代码:
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
Error:
错误:
? 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
Even my editor knows this is a terrible idea...
连我的编辑都知道这是一个糟糕的主意......
回答by Ruslan Ismagilov
Disclaimer: I'm the author of Learn generatorsworkshopper.
免责声明:我是Learn generatorsWorkshopper的作者。
Answer by @slebetman is kinda correct and I also can add more:
@slebetman 的回答有点正确,我还可以添加更多:
Yes, MDN - Iteration Protocoldoesn't refer directly about yield
within callbacks.
But, it tell us about importance from where you yield
item, because you can only use yield
inside generators. See MDN - Iterablesdocs to find out more.
是的,MDN - 迭代协议不直接引用yield
回调。但是,它告诉我们您yield
项目的重要性,因为您只能使用yield
内部generators。请参阅MDN - Iterables文档以了解更多信息。
@marocchinosuggestjust fine solution iterate over Array that was changed after map:
@marocchino建议对映射后更改的 Array 进行很好的解决方案迭代:
function *upper (items) {
yield* items.map(function (item) {
try {
return item.toUpperCase();
} catch (e) {
return null;
}
});
}
We can do it, because Array has iteration mechanism, see Array.prototype[@@iterator]().
我们可以做到,因为 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.mapdoesn't have default iteration behavior, so we couldn't iterate over it.
Array.prototype.map没有默认的迭代行为,所以我们不能迭代它。
But generators is not just iterators. Every generator is an iterator, but not vice versa. Generators allows you to customize iteration (and not only) process by calling yield
keyword. You can play and see the difference between generators/iterators here:
但是生成器不仅仅是迭代器。每个生成器都是迭代器,但反之则不然。生成器允许您通过调用yield
关键字来自定义迭代(不仅仅是)过程。您可以在此处播放并查看生成器/迭代器之间的区别:
Demo: babel/repl.
演示:babel/repl。
回答by slebetman
One problem is yield
yields just one level to the function's caller. So when you yield
in a callback it may not do what you think it does:
一个问题是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'
}
}
}
So in the code above, you have absolutely no access to the yielded value. Array.prototype.map
does have access to the yielded value. And if you were the person who wrote the code for .map()
you can get that value. But since you're not the person who wrote Array.prototype.map
, and since the person who wrote Array.prototype.map
doesn't re-yield the yielded value, you don't get access to the yielded value(s) at all (and hopefully they will be all garbage collected).
因此,在上面的代码中,您绝对无法访问产生的值。Array.prototype.map
确实可以访问产生的值。如果您是为.map()
您编写代码的人,则可以获得该价值。但是因为你不是写的人Array.prototype.map
,而且因为写的人Array.prototype.map
没有重新产生产生的值,你根本无法访问产生的值(希望它们都是垃圾集)。
Can we make it work?
我们可以让它发挥作用吗?
Let's see if we can make yield work in callbacks. We can probably write a function that behaves like .map()
for generators:
让我们看看我们是否可以让 yield 在回调中工作。我们可能可以编写一个行为类似于.map()
生成器的函数:
// WARNING: UNTESTED!
function *mapGen (arr,callback) {
for (var i=0; i<arr.length; i++) {
yield callback(arr[i])
}
}
Then you can use it like this:
然后你可以像这样使用它:
mapGen(items,function (item) {
yield item.toUpperCase();
});
Or if you're brave you can extend Array.prototype
:
或者,如果你很勇敢,你可以扩展Array.prototype
:
// WARNING: UNTESTED!
Array.prototype.mapGen = function *mapGen (callback) {
for (var i=0; i<this.length; i++) {
yield callback(this[i])
}
};
We can probably call it like this:
我们大概可以这样称呼它:
function *upper (items) {
yield* items.mapGen(function * (item) {
try {
yield item.toUpperCase()
} catch (e) {
yield 'null'
}
})
}
Notice that you need to yield twice. That's because the inner yield returns to mapGen
then mapGen
will yield that value then you need to yield it in order to return that value from upper
.
请注意,您需要两次屈服。那是因为内部 yield 返回到mapGen
thenmapGen
将产生该值,那么您需要产生它才能从upper
.
OK. This sort of works but not quite:
行。这种工作但不完全:
var u = upper(['aaa','bbb','ccc']);
console.log(u.next().value); // returns generator object
Not exactly what we want. But it sort of makes sense since the first yield returns a yield. So we process each yield as a generator object? Lets see:
不完全是我们想要的。但这是有道理的,因为第一个收益返回一个收益。那么我们将每个 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
OK. Let's figure out why the second call doesn't work.
行。让我们弄清楚为什么第二个调用不起作用。
The upper function:
上层功能:
function *upper (items) {
yield* items.mapGen(/*...*/);
}
yields the return value of mapGen()
. For now, let's ignore what mapGen
does and just think about what yield
actually means.
产生 的返回值mapGen()
。现在,让我们忽略mapGen
它的作用,而只考虑它的yield
实际含义。
So the first time we call .next()
the function is paused here:
所以我们第一次调用.next()
函数的时候就在这里暂停:
function *upper (items) {
yield* items.mapGen(/*...*/); // <----- yields value and paused
}
which is the first console.log()
. The second time we call .next()
the function call continue at the line after the yield
:
这是第一个console.log()
。我们第二次调用.next()
函数调用 continue 在 之后的行yield
:
function *upper (items) {
yield* items.mapGen(/*...*/);
// <----- function call resumes here
}
which returns (not yield since there's no yield keyword on that line) nothing (undefined).
它返回(不是 yield 因为在该行上没有 yield 关键字)什么都没有(未定义)。
This is why the second console.log()
fails: the *upper()
function has run out of objects to yield. Indeed, it only ever yields once so it has only one object to yield - it is a generator that generates only one value.
这就是第二个console.log()
失败的原因:*upper()
函数已经用完了要让出的对象。事实上,它只产生一次,所以它只有一个要产生的对象——它是一个只产生一个值的生成器。
OK. So we can do it like this:
行。所以我们可以这样做:
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
Yay! But, if this is the case, how can the innermost yield
in the callback work?
耶!但是,如果是这种情况,yield
回调中的最内层如何工作?
Well, if you think carefully you'll realize that the innermost yield
in the callback also behaves like the yield
in *upper()
- it will only ever return one value. But we never use it more than once. That's because the second time we call uu.next()
we're not returning the same callback but another callback which in turn will also ever return only one value.
好吧,如果您仔细考虑,您会发现yield
回调中最内层的行为也与yield
in类似*upper()
- 它只会返回一个值。但我们从未多次使用它。那是因为我们第二次调用时uu.next()
我们没有返回相同的回调,而是另一个回调,而后者也只会返回一个值。
So it works. Or it can be made to work. But it's kind of stupid.
所以它有效。或者它可以工作。但这有点愚蠢。
Conclusion:
结论:
After all this, the key point to realize about why yield
doesn't work the way we expected is that yield
pauses code execution and resumes execution on the next line. If there are no more yields then the generator terminates (is .done
).
毕竟,了解为什么yield
不能按我们预期的方式工作的关键点是yield
暂停代码执行并在下一行恢复执行。如果没有更多的产量,则生成器终止(是.done
)。
Second point to realize is that callbacks and all those Array methods (.map
, .forEach
etc.) aren't magical. They're just javascript functions. As such it's a bit of a mistake to think of them as control structures like for
or while
.
第二点要实现,是回调和所有的阵列方法(.map
,.forEach
等)不神奇。它们只是 javascript 函数。因此,将它们视为像for
or 之类的控制结构有点错误while
。
Epilogue
结语
There is a way to make mapGen
work cleanly:
有一种方法可以使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);
But you'll notice that in this case we return form the callback (not yield) and we also return form upper
. So this case devolves back into a yield
inside a for loop which isn't what we're discussing.
但是您会注意到,在这种情况下,我们返回 form 回调(而不是 yield)并且我们也返回 form upper
。因此,这种情况又回到yield
了 for 循环内部,这不是我们要讨论的内容。
回答by D??ng Nguy?n
You can use another method by "co - npm": co.wrap(fn*)
您可以通过“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)
});