有关详细信息,请参阅此 GitHub 问题:
microsoft/TypeScript#41646 - Allow generic yield* types
通过使用 constrained generic type parameter,生成器函数的返回类型可以从它的参数中派生出来。
type Chain = <Iters extends readonly Iterable<unknown>[]>(
...iters: Iters
) => Iters[number];
在上面的函数签名中,泛型 Iters 必须可分配给 Iterable<unknown> 元素的只读数组类型。因此,其余参数 iters 中的每个参数都必须可分配给 Iterable<unknown>。这将允许编译器推断每个可迭代参数的产生类型。
下面是一个将它应用于您展示的实现的示例,然后将它与您的 playground 链接中的几个示例函数一起使用,以查看推断的返回类型:
declare function test1(): Iterable<number>;
declare function test2(): Iterable<string>;
const chain: Chain = function* (...iters) {
for (let it of iters) yield* it;
};
const iter = chain(test1(), test2());
//^? const iter: Iterable<number> | Iterable<string>
for (const value of iter) {}
//^? const value: string | number
您可以看到推断的返回类型是 Iterable<number> | Iterable<string>,并且在 for...of 循环中使用它会产生一个生成值,该值是并集中每个可迭代对象的生成类型的并集。
我认为这已经根据您的问题标准产生了令人满意的结果,但实际返回类型仍可以改进以更准确地表示返回的迭代器本身。
通过使用类型实用程序(改编自链接的 GitHub 问题中的内容)从可迭代类型中提取产生的类型:
type YieldedFromIterable<
I extends
| Iterable<unknown>
| Iterator<unknown>
| IterableIterator<unknown>
| Generator<unknown>
> = I extends
| Iterable<infer T>
| Iterator<infer T>
| IterableIterator<infer T>
| Generator<infer T>
? T
: never;
...可以为函数创建替代返回类型:
type Chain = <Iters extends readonly Iterable<unknown>[]>(
...iters: Iters
) => Iterable<YieldedFromIterable<Iters[number]>>;
与第一个函数签名的返回类型(它是每个可迭代参数的联合)并列,这个是单个可迭代的,它产生一个从每个参数派生的联合值——使用它看起来像这样:
const chain: Chain = function* (...iters) {
for (let it of iters) yield* it as any;
};
const iter = chain(test1(), test2());
//^? const iter: Iterable<string | number>
for (const value of iter) {}
//^? const value: string | number
Code in TS Playground
结论:
不对第二个示例的生成值使用 type assertion 会导致编译器错误:
const chain: Chain = function* (...iters) { /*
~~~~~
Type '<Iters extends readonly Iterable<unknown>[]>(...iters: Iters) => Generator<unknown, void, undefined>' is not assignable to type 'Chain'.
Call signature return types 'Generator<unknown, void, undefined>' and 'Iterable<YieldedFromIterable<Iters[number]>>' are incompatible.
The types returned by '[Symbol.iterator]().next(...)' are incompatible between these types.
Type 'IteratorResult<unknown, void>' is not assignable to type 'IteratorResult<YieldedFromIterable<Iters[number]>, any>'.
Type 'IteratorYieldResult<unknown>' is not assignable to type 'IteratorResult<YieldedFromIterable<Iters[number]>, any>'.
Type 'IteratorYieldResult<unknown>' is not assignable to type 'IteratorYieldResult<YieldedFromIterable<Iters[number]>>'.
Type 'unknown' is not assignable to type 'YieldedFromIterable<Iters[number]>'.(2322) */
for (let it of iters) yield* it;
};
我认为这是由 limitation of TypeScript's control flow analysis 引起的(但也许我错了,其他人可以提供更清晰的信息)。
一种解决方案是断言值的类型,如下所示:
const chain: Chain = function* (...iters) {
for (let it of iters) yield* it as Iterable<YieldedFromIterable<typeof iters[number]>>; // ok
};
// OR
const chain: Chain = function* (...iters) {
for (let it of iters) yield* it as any; // ok
};