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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-28 12:17:09  来源:igfitidea点击:

Why won't yield return from within a `.map` callback?

javascriptnode.jsgeneratorecmascript-6

提问by Gareth

Learn Generators - 4 ? CATCH ERROR!The solution uses a for loopbut 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 thatbut 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...

连我的编辑都知道这是一个糟糕的主意......

yield within callback

在回调中产生

回答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 yieldwithin callbacks. But, it tell us about importance from where you yielditem, because you can only use yieldinside 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 yieldkeyword. You can play and see the difference between generators/iterators here:

但是生成器不仅仅是迭代器。每个生成器都是迭代器,但反之则不然。生成器允许您通过调用yield关键字来自定义迭代(不仅仅是)过程。您可以在此处播放并查看生成器/迭代器之间的区别:

Demo: babel/repl.

演示babel/repl

回答by slebetman

One problem is yieldyields just one level to the function's caller. So when you yieldin 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.mapdoes 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.mapdoesn'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 mapGenthen mapGenwill yield that value then you need to yield it in order to return that value from upper.

请注意,您需要两次屈服。那是因为内部 yield 返回到mapGenthenmapGen将产生该值,那么您需要产生它才能从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 mapGendoes and just think about what yieldactually 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 yieldin the callback work?

耶!但是,如果是这种情况,yield回调中的最内层如何工作?

Well, if you think carefully you'll realize that the innermost yieldin the callback also behaves like the yieldin *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回调中最内层的行为也与yieldin类似*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 yielddoesn't work the way we expected is that yieldpauses 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, .forEachetc.) 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 foror while.

第二点要实现,是回调和所有的阵列方法(.map.forEach等)不神奇。它们只是 javascript 函数。因此,将它们视为像foror 之类的控制结构有点错误while

Epilogue

结语

There is a way to make mapGenwork 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 yieldinside 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)
});