メインコンテンツへスキップ

TypeScript Generatorの3つのメソッド完全理解:next, return, throw

· loading · loading ·
kiitosu
著者
kiitosu
aws community builder. 画像処理やデバイスドライバ、データ基盤構築からWebバックエンドまで、多様な領域に携わってきました。地図解析や地図アプリケーションの仕組みにも経験があり、幅広い技術を活かした開発に取り組んでいます。休日は草野球とランニングを楽しんでいます。
目次

はじめに
#

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>;
}

出典: TypeScript lib.es2015.generator.d.ts

この型定義を一つずつ解説します。

3つの型パラメータ
#

Generator<T = unknown, TReturn = any, TNext = any>
パラメータ 意味
T yieldで出力される値の型 yield 1T = number
TReturn return文で返される値の型 return "done"TReturn = string
TNext next(value)で渡される値の型 gen.next("input")TNext = string

extends IteratorObject
#

extends IteratorObject<T, TReturn, TNext>

GeneratorIteratorObjectを継承しています。これにより、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]

出典: MDN - Iteration protocols

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 }; // 終了

出典: TypeScript lib.es2015.iterable.d.ts

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...finallyreturn()
  • エラーリカバリが必要ならtry...catchthrow()

Generatorの3つのメソッドを理解することで、より柔軟で堅牢なイテレータパターンを実装できるようになります。

参考リンク
#

Reply by Email

関連記事

gRPC - connect - Render でwebサービスを作ってみる:local buf build for web service
· loading · loading
gRPC - connect - Render でwebサービスを作ってみる:web service with connect
· loading · loading
CDKでNatGatewayのIPアドレスをOutputsに出力する
· loading · loading