为什么不会从 .map 回调中产生 return?
Why won't yield return from within a `.map` callback?
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
连我的编辑都知道这是个糟糕的主意...
一个问题是 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 returns 到 mapGen
然后 mapGen
将产生那个值,然后你需要 yield 它以便 return 从 upper
那个值。
好的。这种作品但不完全是:
var u = upper(['aaa','bbb','ccc']);
console.log(u.next().value); // returns generator object
不完全是我们想要的。但这是有道理的,因为第一个产量 return 是一个产量。所以我们将每个 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()
的 return 值。现在,让我们忽略 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
}
which returns(不是 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
- 它只会 return 一个值。但我们从不使用它超过一次。那是因为我们第二次调用 uu.next()
我们不是 return 同一个回调,而是另一个回调,它反过来也将 return 只有一个值。
所以它有效。或者它可以工作。但这有点愚蠢。
结论:
毕竟,了解为什么 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);
但是您会注意到,在这种情况下,我们 return 形成了回调(而不是让步),我们还 return 形成了 upper
。因此,这种情况又退化为 for 循环内的 yield
,这不是我们正在讨论的内容。
免责声明:我是 Learn generators workshopper.
的作者
@slebetman 的回答有点正确,我还可以补充更多:
是的,MDN - Iteration Protocol 没有在回调中直接引用 yield
。
但是,它告诉我们你 yield
项目的重要性,因为你只能在 generators 中使用 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
关键字来自定义迭代(而不仅仅是)过程。您可以在这里播放并查看 generators/iterators 之间的区别:
演示: babel/repl.
您可以通过"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)
});
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
连我的编辑都知道这是个糟糕的主意...
一个问题是 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 returns 到 mapGen
然后 mapGen
将产生那个值,然后你需要 yield 它以便 return 从 upper
那个值。
好的。这种作品但不完全是:
var u = upper(['aaa','bbb','ccc']);
console.log(u.next().value); // returns generator object
不完全是我们想要的。但这是有道理的,因为第一个产量 return 是一个产量。所以我们将每个 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()
的 return 值。现在,让我们忽略 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
}
which returns(不是 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
- 它只会 return 一个值。但我们从不使用它超过一次。那是因为我们第二次调用 uu.next()
我们不是 return 同一个回调,而是另一个回调,它反过来也将 return 只有一个值。
所以它有效。或者它可以工作。但这有点愚蠢。
结论:
毕竟,了解为什么 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);
但是您会注意到,在这种情况下,我们 return 形成了回调(而不是让步),我们还 return 形成了 upper
。因此,这种情况又退化为 for 循环内的 yield
,这不是我们正在讨论的内容。
免责声明:我是 Learn generators workshopper.
的作者@slebetman 的回答有点正确,我还可以补充更多:
是的,MDN - Iteration Protocol 没有在回调中直接引用 yield
。
但是,它告诉我们你 yield
项目的重要性,因为你只能在 generators 中使用 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
关键字来自定义迭代(而不仅仅是)过程。您可以在这里播放并查看 generators/iterators 之间的区别:
演示: babel/repl.
您可以通过"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)
});