はじめに
この記事では、ジェネレータとイテレータにどのような型が設定されているかをみていきます。
ジェネレータやイテレータ自体については以下でも取り扱っています。
ジェネレータの型
ジェネレータの型は以下のようになっています。
interface Generator<T = unknown, TReturn = any, TNext = unknown> extends Iterator<T, TReturn, TNext> {
// NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places.
next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
return(value: TReturn): IteratorResult<T, TReturn>;
throw(e: any): IteratorResult<T, TReturn>;
[Symbol.iterator](): Generator<T, TReturn, TNext>;
}
ジェネレータの型Generatorは型引数T、TReturn、TNextを取ります。
まずはこのTが何を表しているかについてみてみます。
型引数TはIteratorResultに渡されているため、そちらの型をみてみます。
interface IteratorYieldResult<TYield> {
done?: false;
value: TYield;
}
interface IteratorReturnResult<TReturn> {
done: true;
value: TReturn;
}
type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>;
IteratorResultは反復結果オブジェクトの型であり、doneがtrueのときとfalseのときの型のユニオン型となっています。
この型定義から、Tはyield式の型であり、TReturnは反復結果オブジェクトのdoneプロパティがtrueのときに返される値の型、つまりジェネレータの返り値の型であることがわかります。
また、TNextはnext()メソッドの引数として渡されています。
next()メソッドがyieldに値を渡すときに引数をとりますが、yieldに渡す値の型をTNextとしているわけですね。
ジェネレータはイテレータであり反復可能
ジェネレータはイテレータの一種であり、反復可能なオブジェクトでもあります。
このことが、型の上でも表現されています。
interface Generator<T = unknown, TReturn = any, TNext = unknown> extends Iterator<T, TReturn, TNext>
とあるように、GeneratorはIteratorの部分型であることが必要で、これはジェネレータがイテレータでもあることを型の上で要請しています。
また、Generator型は[Symbol.iterator](): Generator<T, TReturn, TNext>;というメソッドを持つことが規定されており、反復可能であることも型の上で要請されています。
[Symbol.iterator]()の返り値の型がIterator型ではなくGenerator型なのは、ジェネレータの[Symbol.iterator]()は自分自身を返すためです。
function* generatorFn() {
yield 1;
}
const generator = generatorFn();
const genIterator = generator[Symbol.iterator]();
console.log(genIterator === generator); //true
上の例は、ジェネレータとジェネレータの[Symbol.iterator]()メソッドで返されたイテレータが同じオブジェクトであることを示しています。
ジェネレータのメソッドの型
Generatorの型定義から、ジェネレータがもつnext()メソッド、return()メソッド、throw()メソッドがそれぞれ反復結果オブジェクトIteratorResultを返していることが分かります。
next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
return(value: TReturn): IteratorResult<T, TReturn>;
throw(e: any): IteratorResult<T, TReturn>;
next()は引数をもたないか、TNext型の引数を1つだけもち、反復結果オブジェクトが返されることが規定されています。
return()はTReturn型の引数をもち、こちらも反復結果オブジェクトが返されます。
throw()については引数がany型となっており、エラーの他に数値や文字列などの値も渡すこと自体はできます(推奨はされません)。
イテレータの型
さいごに、Iterator型についてもみてみます。
interface Iterator<T, TReturn = any, TNext = any> {
// NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places.
next(...[value]: [] | [TNext]): IteratorResult<T, TReturn>;
return?(value?: TReturn): IteratorResult<T, TReturn>;
throw?(e?: any): IteratorResult<T, TReturn>;
}
イテレータの型もジェネレータの型とほとんど同じものとなっていますが、大きな違いとしては、[Symbol.iterator]()メソッドがない点が挙げられます。
イテレータ自体は必ずしも反復可能ではないため、[Symbol.iterator]()メソッドを型にもっていないわけですね。