在 JavaScript 中传递包含过滤+映射逻辑的生成器的推荐方法是什么?
不知何故,JavaScript 生成器缺少诸如
filter
和 map
操作数之类的基本内容(类似于数组),以便能够创建包含该逻辑的生成器,而无需先运行迭代。
我的直接方法是实现应用逻辑的自定义函数:
function * filter(g, cb) {
let a;
do {
a = g.next();
if (!a.done && cb(a.value)) {
yield a.value;
}
} while (!a.done);
return a.value;
}
function * map(g, cb) {
let a;
do {
a = g.next();
if (!a.done) {
yield cb(a.value);
}
} while (!a.done);
return a.value;
}
但这会造成回调地狱。我想简单地链接一个生成器,就像一个常规数组一样:
// create a filtered & re-mapped generator, without running it:
const gen = myGenerator().filter(a => a > 0).map(b => ({value: b}));
// pass generator into a function that will run it:
processGenerator(gen);
有没有办法扩展生成器以自动访问这些基本功能?
另外,如果有人想了解为什么这些基本的东西不是生成器实现的一部分,那就太棒了!我认为过滤和映射是序列所需的两个最重要的东西。
更新
这一切以我编写自己的 iter-ops 库而结束:)
解决方案的替代方案是使用
for...of
循环而不是 do...while
。
我还更喜欢
filter
和 map
函数来使用和生成如下生成器函数:
function filter(gen, predicate) {
return function*() {
for (let e of gen()) {
if (predicate(e)) {
yield e;
}
}
}
}
function map(gen, fn) {
return function*() {
for (let e of gen()) {
yield fn(e);
}
}
}
function generatorWrapper(gen) {
return {
call: () => gen(),
filter: predicate => generatorWrapper(filter(gen, predicate)),
map: fn => generatorWrapper(map(gen, fn))
};
}
function* generator() {
yield 1;
yield 2;
yield 3;
}
const it = generatorWrapper(generator)
.filter(x => x > 1)
.map(x => x * 2)
.call();
for (let e of it) {
console.log(e);
}
通过管道装饰器获取原始可迭代值和产量值的管道函数怎么样?
const pipe = function* (iterable, decorators) {
// First build the pipeline by iterating over the decorators
// and applying them in sequence.
for(const decorator of decorators) {
iterable = decorator(iterable)
}
// Then yield the values of the composed iterable.
for(const value of iterable) {
yield value;
}
};
const filter = predicate =>
function* (iterable) {
for (const value of iterable) {
if (predicate(value)) {
yield value;
}
}
};
const map = cb =>
function* (iterable) {
for (const value of iterable) {
yield cb(value);
}
};
const mergeMap = cb =>
function* (iterable) {
for (const value of iterable) {
for (const mapped of cb(value)) {
yield mapped;
}
}
};
const take = n =>
function* (iterable) {
for (const value of iterable) {
if (!n--) {
break;
}
yield value;
}
};
function* test(value1, value2) {
yield value1;
yield value2;
}
function* infinite() {
for (;;) yield Math.random();
}
for (const value of pipe(test(111, 222), [
filter(f => f > 0),
map(m => ({ value: m }))
])) {
console.log(value);
}
for (const value of pipe(infinite(), [
take(5),
mergeMap(v => [v, { timesTwo: v * 2 }])
])) {
console.log(value);
}
.as-console-wrapper {
max-height: 100% !important;
}
我可能已经找到了正确的解决方案......
我创建了类
Iterable
,它扩展了这个答案:
class Iterable {
constructor(generator) {
this[Symbol.iterator] = generator;
}
static extend(generator, cc) {
// cc - optional calling context, when generator is a class method;
return function () {
return new Iterable(generator.bind(cc ?? this, ...arguments));
}
}
}
Iterable.prototype.filter = function (predicate) {
let iterable = this;
return new Iterable(function* () {
for (let value of iterable)
if (predicate(value)) {
yield value;
}
});
};
Iterable.prototype.map = function (cb) {
let iterable = this;
return new Iterable(function* () {
for (let value of iterable) {
yield cb(value);
}
});
};
现在我们可以采用现有的生成器函数,如下所示:
function* test(value1, value2) {
yield value1;
yield value2;
}
并将其变成扩展迭代器:
const extTest = Iterable.extend(test);
然后用它代替原来的生成器:
const i = extTest(111, 222).filter(f => f > 0).map(m => ({value: m}));
现在可以正常工作了:
const values = [...i];
//=> [ { value: 111 }, { value: 222 } ]
最终,我编写了自己的 iter-ops 库。
自从 ECMAScript 2025 引入了迭代器辅助方法以来,您希望的代码已经可以工作了,现在实际上可以开箱即用了:
// Your code:
const gen = myGenerator().filter(a => a > 0).map(b => ({value: b}));
processGenerator(gen);
// Some functions that above code is calling:
function* myGenerator() { // A generator for the demo
for (let i = 1; i < 20; i = -i*2) yield i;
}
function processGenerator(gen) { // A consumer of the generator
for (const obj of gen) console.log(obj);
}
filter
和map
方法是迭代器辅助对象的方法,即继承自Iterator
的迭代器。请注意,生成器返回这样的迭代器,就像所有返回迭代器的原生 JS 方法一样,例如 Array.prototype.values
、Object.entries
、...