The Resource Problem
Many operations involve resources that must be cleaned up:- Database connections
- File handles
- Network sockets
- Locks and semaphores
- Memory allocations
acquireRelease - Basic Pattern
acquireRelease pattern ensures resources are released:
import { Effect } from "effect"
const acquire = Effect.gen(function* () {
yield* Effect.log("Opening database connection")
const conn = yield* Effect.tryPromise(() => db.connect())
yield* Effect.log("Connection opened")
return conn
})
import { Effect, Exit } from "effect"
const release = (conn: Connection, exit: Exit.Exit<unknown, unknown>) =>
Effect.gen(function* () {
yield* Effect.log(`Closing connection (${Exit.isSuccess(exit) ? "success" : "failure"})`)
yield* Effect.promise(() => conn.close())
yield* Effect.log("Connection closed")
})
The release function receives the
Exit value, allowing different cleanup based on success or failure.Scope - Resource Lifetime Management
import { Effect } from "effect"
const scoped = Effect.scoped(program)
// All resources in `program` are released when scope ends
import { Effect, Scope } from "effect"
const program = Effect.scopeWith((scope) =>
Scope.addFinalizer(scope, Effect.log("Scope closing"))
)
import { Effect } from "effect"
const program = Effect.gen(function* () {
yield* Effect.acquireRelease(
Effect.log("Acquire 1"),
() => Effect.log("Release 1")
)
yield* Effect.acquireRelease(
Effect.log("Acquire 2"),
() => Effect.log("Release 2")
)
yield* Effect.acquireRelease(
Effect.log("Acquire 3"),
() => Effect.log("Release 3")
)
})
Effect.runPromise(Effect.scoped(program))
// Output:
// Acquire 1
// Acquire 2
// Acquire 3
// Release 3
// Release 2
// Release 1
import { Effect } from "effect"
const program = Effect.gen(function* () {
const outer = yield* Effect.acquireRelease(
Effect.log("Outer acquired"),
() => Effect.log("Outer released")
)
yield* Effect.scoped(
Effect.gen(function* () {
const inner = yield* Effect.acquireRelease(
Effect.log("Inner acquired"),
() => Effect.log("Inner released")
)
// Inner released when this scope ends
})
)
yield* Effect.log("Between scopes")
// Outer released when outer scope ends
})
Effect.runPromise(Effect.scoped(program))
// Output:
// Outer acquired
// Inner acquired
// Inner released
// Between scopes
// Outer released
Guaranteed Cleanup
Resources are released even on errors or interruption:import { Effect } from "effect"
const program = Effect.gen(function* () {
const resource = yield* Effect.acquireRelease(
Effect.log("Acquired"),
() => Effect.log("Released")
)
yield* Effect.fail("Something went wrong!")
// This line is never reached
return "success"
})
Effect.runPromise(Effect.scoped(program)).catch(() => {})
// Output:
// Acquired
// Released
// (then promise rejects)
import { Effect, Fiber } from "effect"
const program = Effect.gen(function* () {
const fiber = yield* Effect.fork(
Effect.gen(function* () {
const resource = yield* Effect.acquireRelease(
Effect.log("Acquired"),
() => Effect.log("Released")
)
yield* Effect.sleep("10 seconds")
return "never gets here"
}).pipe(Effect.scoped)
)
yield* Effect.sleep("100 millis")
yield* Fiber.interrupt(fiber)
})
Effect.runPromise(program)
// Output:
// Acquired
// Released
Advanced Patterns
acquireUseRelease
Combine acquire, use, and release in one call:addFinalizer - Manual Finalizers
Register cleanup functions directly:Shared Resources with Layers
Layers provide shared, scoped resources:Best Practices
import { Effect } from "effect"
// ❌ Bad: Resource leak if error occurs
const bad = Effect.gen(function* () {
const resource = yield* openResource()
const result = yield* useResource(resource)
yield* closeResource(resource)
return result
})
// ✅ Good: Resource always released
const good = Effect.gen(function* () {
const resource = yield* Effect.acquireRelease(
openResource(),
(r) => closeResource(r)
)
return yield* useResource(resource)
}).pipe(Effect.scoped)
import { Effect } from "effect"
// ❌ Bad: Release can fail
const bad = (resource: Resource) =>
Effect.tryPromise(() => resource.close())
// ✅ Good: Release handles errors
const good = (resource: Resource) =>
Effect.tryPromise(() => resource.close()).pipe(
Effect.catchAll((error) =>
Effect.log(`Warning: Failed to close resource: ${error}`)
)
)
import { Effect, Layer, Context } from "effect"
// ❌ Bad: Each effect acquires separately
const bad = Effect.all([
Effect.scoped(useDatabase()),
Effect.scoped(useDatabase()),
Effect.scoped(useDatabase())
])
// Opens and closes database 3 times!
// ✅ Good: Shared via layer
const good = Effect.all([
useDatabase(),
useDatabase(),
useDatabase()
]).pipe(Effect.provide(DatabaseLayer))
// Opens database once, closes when all effects complete
import { Effect } from "effect"
/**
* Acquires a database connection that must be released by the caller.
* Use with Effect.scoped.
*/
const acquireConnection: Effect.Effect<Connection, Error, Scope> = ...
/**
* Uses a database connection. Does not release it.
* The connection must be managed by the caller.
*/
const useConnection: (conn: Connection) => Effect.Effect<Result> = ...
Common Patterns
File Operations
Lock Management
Timeout with Cleanup
All 7 core concept pages have been created with real type signatures and examples from the Effect source code.