【问题标题】:Why won't yield return from within a `.map` callback?为什么不会从 .map 回调中产生返回?
【发布时间】:2015-08-10 11:05:33
【问题描述】:

Learn Generators - 4 » CATCH ERROR! 该解决方案使用for loop,但我在MDN - Iteration Protocols 中找不到任何涉及回调中的yield 的内容。

我猜答案只是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

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

【问题讨论】:

    标签: javascript node.js generator ecmascript-6


    【解决方案1】:

    你可以通过“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)
    });
    

    【讨论】:

      【解决方案2】:

      一个问题是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 的人没有重新产生产生的值,所以您根本无法访问产生的值(并且希望它们都被垃圾收集)。

      我们可以让它工作吗?

      让我们看看是否可以让 yield 在回调中起作用。我们或许可以为生成器编写一个行为类似于.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'
          }
        })
      }
      

      请注意,您需要让步两次。这是因为内部 yield 返回到 mapGen 然后 mapGen 将产生该值,然后您需要 yield 它才能从 upper 返回该值。

      好的。这种工作但不完全:

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

      不完全是我们想要的。但这有点道理,因为第一个收益率会返回一个收益率。所以我们把每个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
      

      好的。让我们弄清楚为什么第二次调用不起作用。

      上层函数:

      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,因为该行没有 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 的行为也类似于*upper() 中的yield - 它只会返回一个值。但我们从不多次使用它。这是因为我们第二次调用 uu.next() 时,我们返回的不是同一个回调,而是另一个回调,而另一个回调也只会返回一个值。

      所以它起作用了。或者它可以工作。但这有点愚蠢。

      结论:

      毕竟,要了解为什么yield 不能按我们预期的方式工作的关键是yield 暂停代码执行并在下一行继续执行。如果没有更多的收益,则生成器终止(.done)。

      要意识到的第二点是回调和所有这些 Array 方法(.map.forEach 等)并不神奇。它们只是 javascript 函数。因此,将它们视为forwhile 之类的控制结构有点错误。

      结语

      有一种方法可以让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,这不是我们要讨论的。

      【讨论】:

      • 我从来没有使用过yield,所以最后一个例子可能是错误的。它可能需要回报而不是收益。
      • 您可能希望在最后一个示例中使用yield* items.mapGen(...)。或者将upper 设为普通函数,将return items.mapGen(...) 设为。
      • 不行。它仍然在yield item.toUpperCase() Gist - solution.js 上窒息
      • @gyaresu:啊,是的。在这种情况下,mapGen 的回调不应包含 yield。它应该只是一个return
      • 在玩弄了这个之后,我得出的结论是它不适用于回调,因为它没有意义。注意:它可能看起来对人类应该有意义,但如果你像解释器一样思考,你会发现它没有意义。我会更新我的答案。
      【解决方案3】:

      免责声明:我是Learn generators Workshopper 的作者

      @slebetman 的回答有点正确,我还可以补充:

      是的,MDN - Iteration Protocol 不会在回调中直接引用 yield。 但是,它告诉我们你在哪里yield 项目的重要性,因为你只能在 generators 中使用yield。请参阅MDN - Iterables 文档以了解更多信息。

      @marocchinosuggest 很好的解决方案遍历映射后更改的数组:

      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.

      【讨论】:

      • 一般map用于将一个数组转换为另一个数组。为什么这行不通? bit.ly/1YPHiYS - 即 a 未定义 - 有办法吗?
      猜你喜欢
      • 2020-04-15
      • 1970-01-01
      • 2010-11-28
      • 2021-12-19
      • 1970-01-01
      • 1970-01-01
      • 2019-06-12
      • 1970-01-01
      • 2016-11-22
      相关资源
      最近更新 更多