Effect.addFinalizer
実行中のScope (effect)にfinalizerを追加する
finalizerはscopeが閉じられるときに必ず実行される
成功・失敗・中断に関わらず実行される
https://effect.website/docs/resource-management/scope/#addfinalizer
例:成功時にfinalizerを追加
code:ts
import { Effect, Console } from "effect"
// program :: Effect<string, never, Scope>
const program = Effect.gen(function* () {
yield* Effect.addFinalizer((exit) =>
Console.log(Finalizer executed. Exit status: ${exit._tag})
)
return "some result"
})
const runnable = Effect.scoped(program)
Effect.runPromiseExit(runnable).then(console.log)
programの型はEffect<string, never, Scope>
Scopeの提供が必要なEffectであることを示している
Effect.scopedを使うことで、新しいscopeが作成され、その中でeffectが実行され、finalizerも適切に処理される
Effect.addFinalizerのモチベってよくわからんくない?mrsekut.icon
本当は、リソースの取得と解放をセットにしたいのに、addFinalizerは解放のみにしか言及してない
そういう意味で、Effect.acquireReleaseを使ったほうが意図を理解しやすい
周囲に、リソース取得している処理が見当たらない場合、何のためにfinalizer書いているの?となってしまう
gpt-5.icon曰く、取得と解放が1:1で結びつかない場合に使うと良いらしい
まあそれはわかるmrsekut.icon
addFinalierは、この Scope が終わるときに必ず実行してほしいという処理を登録する API
ただし、それがどのリソースのためかは API 自体は知らない
例: cleanup が後から決まる / 条件で変わる
処理の途中で「もし A が起きたら B を解放する必要がある」
e.g. 動的に追加される temporary file
e.g. retry の中で条件次第で後始末が必要になる
code:ts
if (shouldCreateTempFile) {
const f = await createTempFile()
yield* Effect.addFinalizer(() =>
Effect.sync(() => fs.unlinkSync(f.path))
)
}
この場合、acquireRelease ではモデリングできない
例: 複数リソースを“まとめて”1つの finalizer にしたい
e.g. ログ出力やメトリクスを Scope 終了時に出したい
code:ts
yield* Effect.addFinalizer(() =>
Effect.sync(() => console.log("scope finished"))
)
これは「リソース解放」ではなく、「スコープが終わったら何かしたい」
acquireRelease は本来“リソースの解放”のための仕組みなので適さない