はじめに #
Generatorのnext()は使ったことがあっても、return()やthrow()メソッドを使いこなしている人は少ないのではないでしょうか。
この記事では、Generatorが持つ3つのメソッド(next(), return(), throw())を完全に理解し、それらを組み合わせた実践的な例を実装します。
function* とは #
Generator関数はfunction*という構文で宣言します。*(アスタリスク)は「この関数はGeneratorを生成する」という宣言です。
// 通常の関数
function normal() {
console.log("実行された");
return 1;
}
const result1 = normal(); // "実行された" が出力される
console.log(result1); // 1
// Generator関数
function* generator() {
console.log("実行された");
yield 1;
}
const gen = generator(); // まだ何も出力されない
const result2 = gen.next(); // ここで初めて "実行された" が出力される
console.log(result2); // { value: 1, done: false }
console.log(result2.value); // 1
| 構文 | 呼び出し結果 |
|---|---|
function foo() {} |
関数本体を実行し、戻り値を返す |
function* foo() {} |
実行せず、Generatorオブジェクトを返す |
Generator関数を呼び出すと、関数本体は実行されず、代わりにGeneratorオブジェクトが返されます。next()を呼んで初めて、関数本体が実行されます。
出典: MDN - function*
Generator型の定義を確認する #
まず、TypeScriptにおけるGenerator型の定義を見てみましょう。
interface Generator<T = unknown, TReturn = any, TNext = any> extends IteratorObject<T, TReturn, TNext> {
next(...[value]: [] | [TNext]): IteratorResult<T, TReturn>;
return(value: TReturn): IteratorResult<T, TReturn>;
throw(e: any): IteratorResult<T, TReturn>;
[Symbol.iterator](): Generator<T, TReturn, TNext>;
}
この型定義を一つずつ解説します。
3つの型パラメータ #
Generator<T = unknown, TReturn = any, TNext = any>
| パラメータ | 意味 | 例 |
|---|---|---|
T |
yieldで出力される値の型 |
yield 1 → T = number |
TReturn |
return文で返される値の型 |
return "done" → TReturn = string |
TNext |
next(value)で渡される値の型 |
gen.next("input") → TNext = string |
extends IteratorObject #
extends IteratorObject<T, TReturn, TNext>
GeneratorはIteratorObjectを継承しています。これにより、Generatorはイテレータプロトコルに準拠し、for...ofループやスプレッド構文で使用できます。
function* nums() { yield 1; yield 2; yield 3; }
// for...of で使える
for (const n of nums()) { console.log(n); }
// スプレッド構文で使える
const arr = [...nums()]; // [1, 2, 3]
next()のシグネチャ #
next(...[value]: [] | [TNext]): IteratorResult<T, TReturn>;
...[value]: [] | [TNext]という独特な記法は、引数が0個または1個であることを表します。
gen.next()- 引数なし([]にマッチ)gen.next("input")- 引数1個([TNext]にマッチ)
戻り値のIteratorResult<T, TReturn>は以下の型です:
type IteratorResult<T, TReturn> =
| { value: T; done: false } // まだ続く
| { value: TReturn; done: true }; // 終了
Symbol.iterator #
[Symbol.iterator](): Generator<T, TReturn, TNext>;
これはGenerator自身がイテラブルであることを意味します。[Symbol.iterator]()を呼ぶと自分自身を返します。
const gen = nums();
console.log(gen[Symbol.iterator]() === gen); // true
この設計により、Generatorはイテレータであると同時にイテラブルでもあります。
3つのメソッド早わかり #
next(value) - 双方向通信 #
next()は値を受け取るだけでなく、送り込むこともできます。
function* echo(): Generator<string, void, string> {
let input = yield "最初のメッセージ";
while (true) {
input = yield `受け取った: ${input}`;
}
}
const gen = echo();
console.log(gen.next().value); // "最初のメッセージ"
console.log(gen.next("Hello").value); // "受け取った: Hello"
console.log(gen.next("World").value); // "受け取った: World"
ポイント:yield式の評価値がnext()に渡した引数になります。
return(value) - 早期終了 + finally実行 #
return()を呼ぶと、generatorは即座に終了します。ただしfinallyブロックは実行されます。
function* withCleanup(): Generator<number, string, void> {
try {
yield 1;
yield 2;
yield 3;
} finally {
console.log("クリーンアップ実行!");
}
}
const gen = withCleanup();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.return("終了")); // クリーンアップ実行! → { value: "終了", done: true }
console.log(gen.next()); // { value: undefined, done: true }
for...ofでbreakした時も、内部でreturn()が呼ばれます。
throw(error) - 例外を注入 #
throw()は、generator内の現在のyield位置に例外を投げ込みます。
function* withErrorHandling(): Generator<number, void, void> {
try {
yield 1;
yield 2;
} catch (e) {
console.log(`エラーをキャッチ: ${e}`);
yield -1; // リカバリ値
}
}
const gen = withErrorHandling();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.throw("問題発生")); // エラーをキャッチ: 問題発生 → { value: -1, done: false }
実践:3つを全て使う統合例 #
3つのメソッドをフル活用した「対話型データプロセッサ」を作ってみましょう。
type ProcessorResult = {
processed: number;
lastValue: string | null;
};
function* interactiveProcessor(): Generator<
ProcessorResult,
string, // return時の型
string | undefined // next()で渡す型
> {
let processedCount = 0;
let lastValue: string | null = null;
console.log("[Processor] 開始");
try {
while (true) {
// next(value)で受け取った値を処理
const input = yield { processed: processedCount, lastValue };
if (input !== undefined) {
console.log(`[Processor] 処理中: "${input}"`);
lastValue = input.toUpperCase();
processedCount++;
}
}
} catch (e) {
// throw()でエラーを受け取った場合のリカバリ
console.log(`[Processor] エラー発生、リカバリ: ${e}`);
lastValue = "[ERROR]";
// リカバリ後も継続可能
yield { processed: processedCount, lastValue };
} finally {
// return()または正常終了時のクリーンアップ
console.log(`[Processor] 終了。合計処理数: ${processedCount}`);
}
return `完了: ${processedCount}件処理`;
}
// 使用例
const processor = interactiveProcessor();
// 1. 初期化(最初のnext())
console.log("--- 初期化 ---");
console.log(processor.next());
// [Processor] 開始
// { value: { processed: 0, lastValue: null }, done: false }
// 2. データを送り込む(next(value)で双方向通信)
console.log("--- データ処理 ---");
console.log(processor.next("hello"));
// [Processor] 処理中: "hello"
// { value: { processed: 1, lastValue: 'HELLO' }, done: false }
console.log(processor.next("world"));
// [Processor] 処理中: "world"
// { value: { processed: 2, lastValue: 'WORLD' }, done: false }
// 3. エラーを注入(throw()でエラーハンドリング)
console.log("--- エラー注入 ---");
console.log(processor.throw(new Error("テストエラー")));
// [Processor] エラー発生、リカバリ: Error: テストエラー
// { value: { processed: 2, lastValue: '[ERROR]' }, done: false }
// 4. 早期終了(return()でクリーンアップ実行)
console.log("--- 早期終了 ---");
console.log(processor.return("キャンセル"));
// [Processor] 終了。合計処理数: 2
// { value: 'キャンセル', done: true }
この例のポイント #
| メソッド | この例での役割 |
|---|---|
next(value) |
データを送り込み、処理結果を受け取る |
return(value) |
処理を中断し、finallyでリソース解放 |
throw(error) |
エラーを注入し、catchでリカバリ処理 |
まとめ:3メソッドの使い分け #
| メソッド | 用途 | finally実行 |
|---|---|---|
next(value) |
通常の反復、双方向通信 | - |
return(value) |
早期終了、リソース解放 | される |
throw(error) |
エラー通知、リカバリ処理 | catchがなければされる |
実践的なガイドライン:
- 普段は
next()だけで十分 - リソース管理が必要なら
try...finallyとreturn() - エラーリカバリが必要なら
try...catchとthrow()
Generatorの3つのメソッドを理解することで、より柔軟で堅牢なイテレータパターンを実装できるようになります。
参考リンク #
- MDN - Generator - Generator オブジェクトの公式ドキュメント
- MDN - Generator.prototype.next()
- MDN - Generator.prototype.return()
- MDN - Generator.prototype.throw()
- MDN - Iteration protocols - イテレータプロトコルの詳細
- TypeScript lib.es2015.generator.d.ts - TypeScriptの型定義