为什么不在`.map`回调中产生回报?

Gar*_*eth 15 javascript generator node.js ecmascript-6

学习发电机 - 4»CATCH ERROR! 该解决方案使用了一个for loop但我在MDN中找不到任何东西- 迭代协议指的是回调中的收益.

我会猜测答案只是,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
Run Code Online (Sandbox Code Playgroud)

错误:

?  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
Run Code Online (Sandbox Code Playgroud)

甚至我的编辑都知道这是一个可怕的想法......

回调中的收益率

Rus*_*lov 16

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

回答@slebetman是正确的,我也可以添加更多:

是的,MDN - 迭代协议没有直接引用yield回调内部.但是,它告诉我们您从哪个yield项目的重要性,因为您只能使用yield内部生成器.请参阅MDN - Iterables文档以了解更多信息.

@marocchino 建议很好的解决方案迭代在map之后更改的Array:

function *upper (items) {
  yield* items.map(function (item) {
    try {
      return item.toUpperCase();
    } catch (e) {
      return null;
    }
  });
}
Run Code Online (Sandbox Code Playgroud)

我们可以这样做,因为Array有迭代机制,参见Array.prototype [@@ iterator]().

var bad_items = ['a', 'B', 1, 'c'];

for (let item of bad_items) {
  console.log(item); // a B 1 c
}
Run Code Online (Sandbox Code Playgroud)

Array.prototype.map没有默认的迭代行为,因此我们无法迭代它.

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

演示:babel/repl.


sle*_*man 15

一个问题是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'
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

因此,在上面的代码中,您绝对无法访问产生的值.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])
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以像这样使用它:

mapGen(items,function (item) {
    yield item.toUpperCase();
});
Run Code Online (Sandbox Code Playgroud)

或者,如果你是勇敢的,你可以扩展Array.prototype:

// WARNING: UNTESTED!
Array.prototype.mapGen = function *mapGen (callback) {
    for (var i=0; i<this.length; i++) {
        yield callback(this[i])
    }
};
Run Code Online (Sandbox Code Playgroud)

我们可以这样称呼它:

function *upper (items) {
  yield* items.mapGen(function * (item) {
    try {
      yield item.toUpperCase()
    } catch (e) {
      yield 'null'
    }
  })
}
Run Code Online (Sandbox Code Playgroud)

请注意,您需要两次屈服.这是因为内部收益率返回到mapGen那时mapGen将产生该值,然后您需要产生它以便从中返回该值upper.

好.这种工作但不完全:

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

不完全是我们想要的.但这有点意义,因为第一次收益率会产生收益率.那么我们将每个产量作为生成对象处理?让我们来看看:

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
Run Code Online (Sandbox Code Playgroud)

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

上层功能:

function *upper (items) {
  yield* items.mapGen(/*...*/);
}
Run Code Online (Sandbox Code Playgroud)

产生的回报值mapGen().现在,让我们忽略mapGen它的作用,只考虑yield实际意义.

所以我们第一次调用.next()函数时会暂停:

function *upper (items) {
  yield* items.mapGen(/*...*/); // <----- yields value and paused
}
Run Code Online (Sandbox Code Playgroud)

这是第一个console.log().我们第二次调用.next()函数调用继续在以下行之后yield:

function *upper (items) {
  yield* items.mapGen(/*...*/);
  // <----- function call resumes here
}
Run Code Online (Sandbox Code Playgroud)

返回(因为该行上没有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
Run Code Online (Sandbox Code Playgroud)

好极了!但是,如果是这种情况,yield回调中最内层的工作原理如何?

好吧,如果你仔细想想你会发现yield回调中最内层的行为也像yieldin *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);
Run Code Online (Sandbox Code Playgroud)

但是你会注意到在这种情况下我们返回回调(不是yield),我们也返回表单upper.所以这个案例又回到了yield一个for循环中,这不是我们正在讨论的内容.