JS: ジェネレーターは .return() / .throw() で外から止めたり例外を入れたりできる
ジェネレーターのオブジェクトは next() のほかに、外から終了させる return(value) と、止まっている yield の位置に例外を投げ込む throw(error) を持っている。どちらも中の finally が動くので、後片付けをジェネレーターの中に書ける。
使う場面
- ファイルや接続を開いて 1 件ずつ
yieldするジェネレーターで、呼び出し側が途中で抜けても確実に閉じたい - 呼び出し側で起きたエラー(タイムアウトなど)をジェネレーターの中に知らせて、中で後始末や別の処理をさせたい
やり方
js
function* rows() {
console.log("open");
try {
yield 1;
yield 2;
yield 3;
} catch (e) {
console.log("中で受け取った:", e.message);
yield -1; // 例外を受けて続行もできる
} finally {
console.log("close"); // return() でも throw() でも必ず通る
}
}
// 1) return(): 外から終わらせる
const a = rows();
a.next(); // open → { value: 1, done: false }
a.return("stop"); // close → { value: "stop", done: true }
// 2) throw(): 止まっている yield の位置で例外が起きる
const b = rows();
b.next(); // { value: 1, done: false }
b.throw(new Error("timeout")); // 中で受け取った: timeout → { value: -1, done: false }
b.next(); // close → { value: undefined, done: true }for...of を break や return・例外で抜けると、言語が自動で return() を呼ぶ。だから後片付けは finally に書いておけば、呼び出し側は何も意識しなくてよい。
js
for (const r of rows()) {
if (r === 2) break; // ここで return() が呼ばれ、"close" が出る
}注意点
finallyの中にyieldがあると、return()しても終わらずにその値をdone: falseで返して止まる。終了を止められてしまうので、finallyでyieldは書かない。- 中で
catchしなかった例外はthrow()の呼び出し元に投げ返され、ジェネレーターは終了する。 - まだ一度も
next()していないジェネレーターにthrow()すると、中のtryに入る前なので受け取れず、そのまま外に投げ返される。 - 非同期ジェネレーター(
async function*)でも同じで、return()/throw()は Promise を返す。for await...ofを抜けたときもreturn()が呼ばれる。
出典
- @__syumai(ECMAScript 仕様輪読会 #esspec で Generator を読んだ感想)