Introduction
In long-running applications, managing resources efficiently is essential, particularly when building large-scale systems. If resources like socket connections, database connections, or file descriptors are not properly managed, it can lead to resource leaks, which degrade application performance and reliability. Effect provides constructs that help ensure resources are properly managed and released, even in cases where exceptions occur.
By ensuring that every time a resource is acquired, there is a corresponding mechanism to release it, Effect simplifies the process of resource management in your application.
Finalization
In many programming languages, the try / finally construct ensures that cleanup code runs regardless of whether an operation succeeds or fails. Effect provides similar functionality through Effect.ensuring, Effect.onExit, and Effect.onError.
ensuring
The Effect.ensuring function guarantees that a finalizer effect runs whether the main effect succeeds, fails, or is interrupted.
This is useful for performing cleanup actions such as closing file handles, logging messages, or releasing locks.
If you need access to the effect’s result, consider using onExit.
Example (Running a Finalizer in All Outcomes)
import { Console, Effect } from "effect"
// Define a cleanup effectconst handler = Effect.ensuring(Console.log("Cleanup completed"))
// Define a successful effectconst success = Console.log("Task completed").pipe(Effect.as("some result"), handler)
Effect.runFork(success)/*Output:Task completedCleanup completed*/
// Define a failing effectconst failure = Console.log("Task failed").pipe(Effect.andThen(Effect.fail("some error")), handler)
Effect.runFork(failure)/*Output:Task failedCleanup completed*/
// Define an interrupted effectconst interruption = Console.log("Task interrupted").pipe(Effect.andThen(Effect.interrupt), handler)
Effect.runFork(interruption)/*Output:Task interruptedCleanup completed*/onExit
Effect.onExit allows you to run a cleanup effect after the main effect completes, receiving an Exit value that describes the outcome.
- If the effect succeeds, the
Exitholds the success value. - If it fails, the
Exitincludes the error or failure cause. - If it is interrupted, the
Exitreflects that interruption.
The cleanup step itself is uninterruptible, which can help manage resources in complex or high-concurrency cases.
Example (Running a Cleanup Function with the Effect’s Result)
import { Console, Effect, Exit } from "effect"
// Define a cleanup effect that logs the resultconst handler = Effect.onExit((exit) => Console.log(`Cleanup completed: ${Exit.getOrElse(exit, String)}`),)
// Define a successful effectconst success = Console.log("Task completed").pipe(Effect.as("some result"), handler)
Effect.runFork(success)/*Output:Task completedCleanup completed: some result*/
// Define a failing effectconst failure = Console.log("Task failed").pipe(Effect.andThen(Effect.fail("some error")), handler)
Effect.runFork(failure)/*Output:Task failedCleanup completed: Error: some error*/
// Define an interrupted effectconst interruption = Console.log("Task interrupted").pipe(Effect.andThen(Effect.interrupt), handler)
Effect.runFork(interruption)/*Output:Task interruptedCleanup completed: All fibers interrupted without errors.*/onError
This function lets you attach a cleanup effect that runs whenever the calling effect fails, passing the cause of the failure to the cleanup effect.
You can use it to perform actions such as logging, releasing resources, or applying additional recovery steps.
The cleanup effect will also run if the failure is caused by interruption, and it is uninterruptible, so it always finishes once it starts.
Example (Running Cleanup Only on Failure)
import { Console, Effect } from "effect"
// This handler logs the failure cause when the effect failsconst handler = Effect.onError((cause) => Console.log(`Cleanup completed: ${cause}`))
// Define a successful effectconst success = Console.log("Task completed").pipe(Effect.as("some result"), handler)
Effect.runFork(success)/*Output:Task completed*/
// Define a failing effectconst failure = Console.log("Task failed").pipe(Effect.andThen(Effect.fail("some error")), handler)
Effect.runFork(failure)/*Output:Task failedCleanup completed: Error: some error*/
// Define a failing effectconst defect = Console.log("Task failed with defect").pipe( Effect.andThen(Effect.die("Boom!")), handler,)
Effect.runFork(defect)/*Output:Task failed with defectCleanup completed: Error: Boom!*/
// Define an interrupted effectconst interruption = Console.log("Task interrupted").pipe(Effect.andThen(Effect.interrupt), handler)
Effect.runFork(interruption)/*Output:Task interruptedCleanup completed: All fibers interrupted without errors.*/acquireUseRelease
Many real-world operations involve working with resources that must be released when no longer needed, such as:
- Database connections
- File handles
- Network requests
Effect provides Effect.acquireUseRelease, which ensures that a resource is:
- Acquired properly.
- Used for its intended purpose.
- Released even if an error occurs.
Syntax
Effect.acquireUseRelease(acquire, use, release)Example (Automatically Managing Resource Lifetime)
import { Effect, Console } from "effect"
// Define an interface for a resourceinterface MyResource { readonly contents: string readonly close: () => Promise<void>}
// Simulate resource acquisitionconst getMyResource = (): Promise<MyResource> => Promise.resolve({ contents: "lorem ipsum", close: () => new Promise((resolve) => { console.log("Resource released") resolve() }), })
// Define how the resource is acquiredconst acquire = Effect.tryPromise({ try: () => getMyResource().then((res) => { console.log("Resource acquired") return res }), catch: () => new Error("getMyResourceError"),})
// Define how the resource is releasedconst release = (res: MyResource) => Effect.promise(() => res.close())
const use = (res: MyResource) => Console.log(`content is ${res.contents}`)
// ┌─── Effect<void, Error, never>// ▼const program = Effect.acquireUseRelease(acquire, use, release)
Effect.runPromise(program)/*Output:Resource acquiredcontent is lorem ipsumResource released*/