はじめに
この記事では、ジェネレータとイテレータにどのような型が設定されているかをみていきます。
ジェネレータやイテレータ自体については以下でも取り扱っています。
ジェネレータの型
ジェネレータの型は以下のようになっています。
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]()
メソッドを型にもっていないわけですね。