In certain scenarios, you might need to perform a sequence of chained operations where the success of each operation depends on the previous one. However, if any of the operations fail, you would want to reverse the effects of all previous successful operations. This pattern is valuable when you need to ensure that either all operations succeed, or none of them have any effect at all.
Effect offers a way to achieve this pattern using the Effect.acquireRelease function in combination with the Exit type.
The Effect.acquireRelease function allows you to acquire a resource, perform operations with it, and release the resource when you’re done.
The Exit type represents the outcome of an effectful computation, indicating whether it succeeded or failed.
Let’s go through an example of implementing this pattern. Suppose we want to create a “Workspace” in our application, which involves creating an S3 bucket, an ElasticSearch index, and a Database entry that relies on the previous two.
To begin, we define the domain model for the required services:
S3
ElasticSearch
Database
Next, we define the three create actions and the overall transaction (make) for the
The Effect interface defines a value that lazily describes a workflow or job.
The workflow requires some context R, and may fail with an error of type E,
or succeed with a value of type A.
Effect values model resourceful interaction with the outside world, including
synchronous, asynchronous, concurrent, and parallel interaction. They use a
fiber-based concurrency model, with built-in support for scheduling, fine-grained
interruption, structured concurrency, and high scalability.
To run an Effect value, you need a Runtime, which is a type that is capable
of executing Effect values.
The Effect interface defines a value that lazily describes a workflow or job.
The workflow requires some context R, and may fail with an error of type E,
or succeed with a value of type A.
Effect values model resourceful interaction with the outside world, including
synchronous, asynchronous, concurrent, and parallel interaction. They use a
fiber-based concurrency model, with built-in support for scheduling, fine-grained
interruption, structured concurrency, and high scalability.
To run an Effect value, you need a Runtime, which is a type that is capable
of executing Effect values.
The Effect interface defines a value that lazily describes a workflow or job.
The workflow requires some context R, and may fail with an error of type E,
or succeed with a value of type A.
Effect values model resourceful interaction with the outside world, including
synchronous, asynchronous, concurrent, and parallel interaction. They use a
fiber-based concurrency model, with built-in support for scheduling, fine-grained
interruption, structured concurrency, and high scalability.
To run an Effect value, you need a Runtime, which is a type that is capable
of executing Effect values.
@since ― 2.0.0
@since ― 2.0.0
Effect<
interfaceIndex
Index,
classElasticSearchError
ElasticSearchError>
31
readonly
deleteIndex: (index:Index) => Effect.Effect<void>
deleteIndex: (
index: Index
index:
interfaceIndex
Index) =>
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect.
interfaceEffect<outA, outE=never, outR=never>
The Effect interface defines a value that lazily describes a workflow or job.
The workflow requires some context R, and may fail with an error of type E,
or succeed with a value of type A.
Effect values model resourceful interaction with the outside world, including
synchronous, asynchronous, concurrent, and parallel interaction. They use a
fiber-based concurrency model, with built-in support for scheduling, fine-grained
interruption, structured concurrency, and high scalability.
To run an Effect value, you need a Runtime, which is a type that is capable
of executing Effect values.
The Effect interface defines a value that lazily describes a workflow or job.
The workflow requires some context R, and may fail with an error of type E,
or succeed with a value of type A.
Effect values model resourceful interaction with the outside world, including
synchronous, asynchronous, concurrent, and parallel interaction. They use a
fiber-based concurrency model, with built-in support for scheduling, fine-grained
interruption, structured concurrency, and high scalability.
To run an Effect value, you need a Runtime, which is a type that is capable
of executing Effect values.
@since ― 2.0.0
@since ― 2.0.0
Effect<
interfaceEntry
Entry,
classDatabaseError
DatabaseError>
50
readonly
deleteEntry: (entry:Entry) => Effect.Effect<void>
deleteEntry: (
entry: Entry
entry:
interfaceEntry
Entry) =>
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect.
interfaceEffect<outA, outE=never, outR=never>
The Effect interface defines a value that lazily describes a workflow or job.
The workflow requires some context R, and may fail with an error of type E,
or succeed with a value of type A.
Effect values model resourceful interaction with the outside world, including
synchronous, asynchronous, concurrent, and parallel interaction. They use a
fiber-based concurrency model, with built-in support for scheduling, fine-grained
interruption, structured concurrency, and high scalability.
To run an Effect value, you need a Runtime, which is a type that is capable
of executing Effect values.
@since ― 2.0.0
@since ― 2.0.0
Effect<void>
51
}
52
>() {}
53
54
// Create a bucket, and define the release function that deletes the
This function constructs a scoped resource from an acquire and releaseEffect value.
If the acquireEffect value successfully completes execution, then the
releaseEffect value will be added to the finalizers associated with the
scope of this Effect value, and it is guaranteed to be run when the scope
is closed.
The acquire and releaseEffect values will be run uninterruptibly.
Additionally, the releaseEffect value may depend on the Exit value
specified when the scope is closed.
@param ― acquire - The Effect value that acquires the resource.
@param ― release - The Effect value that releases the resource.
@returns ― A new Effect value that represents the scoped resource.
This function constructs a scoped resource from an acquire and releaseEffect value.
If the acquireEffect value successfully completes execution, then the
releaseEffect value will be added to the finalizers associated with the
scope of this Effect value, and it is guaranteed to be run when the scope
is closed.
The acquire and releaseEffect values will be run uninterruptibly.
Additionally, the releaseEffect value may depend on the Exit value
specified when the scope is closed.
@param ― acquire - The Effect value that acquires the resource.
@param ― release - The Effect value that releases the resource.
@returns ― A new Effect value that represents the scoped resource.
This function constructs a scoped resource from an acquire and releaseEffect value.
If the acquireEffect value successfully completes execution, then the
releaseEffect value will be added to the finalizers associated with the
scope of this Effect value, and it is guaranteed to be run when the scope
is closed.
The acquire and releaseEffect values will be run uninterruptibly.
Additionally, the releaseEffect value may depend on the Exit value
specified when the scope is closed.
@param ― acquire - The Effect value that acquires the resource.
@param ― release - The Effect value that releases the resource.
@returns ― A new Effect value that represents the scoped resource.
Scopes all resources used in this workflow to the lifetime of the workflow,
ensuring that their finalizers are run as soon as this workflow completes
execution, whether by success, failure, or interruption.
We then create simple service implementations to test the behavior of our Workspace code.
To achieve this, we will utilize layers to construct test
These layers will be able to handle various scenarios, including errors, which we can control using the FailureCase type.
The Effect interface defines a value that lazily describes a workflow or job.
The workflow requires some context R, and may fail with an error of type E,
or succeed with a value of type A.
Effect values model resourceful interaction with the outside world, including
synchronous, asynchronous, concurrent, and parallel interaction. They use a
fiber-based concurrency model, with built-in support for scheduling, fine-grained
interruption, structured concurrency, and high scalability.
To run an Effect value, you need a Runtime, which is a type that is capable
of executing Effect values.
The Effect interface defines a value that lazily describes a workflow or job.
The workflow requires some context R, and may fail with an error of type E,
or succeed with a value of type A.
Effect values model resourceful interaction with the outside world, including
synchronous, asynchronous, concurrent, and parallel interaction. They use a
fiber-based concurrency model, with built-in support for scheduling, fine-grained
interruption, structured concurrency, and high scalability.
To run an Effect value, you need a Runtime, which is a type that is capable
of executing Effect values.
The Effect interface defines a value that lazily describes a workflow or job.
The workflow requires some context R, and may fail with an error of type E,
or succeed with a value of type A.
Effect values model resourceful interaction with the outside world, including
synchronous, asynchronous, concurrent, and parallel interaction. They use a
fiber-based concurrency model, with built-in support for scheduling, fine-grained
interruption, structured concurrency, and high scalability.
To run an Effect value, you need a Runtime, which is a type that is capable
of executing Effect values.
@since ― 2.0.0
@since ― 2.0.0
Effect<
interfaceIndex
Index,
classElasticSearchError
ElasticSearchError>
31
readonly
deleteIndex: (index:Index) => Effect.Effect<void>
deleteIndex: (
index: Index
index:
interfaceIndex
Index) =>
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect.
interfaceEffect<outA, outE=never, outR=never>
The Effect interface defines a value that lazily describes a workflow or job.
The workflow requires some context R, and may fail with an error of type E,
or succeed with a value of type A.
Effect values model resourceful interaction with the outside world, including
synchronous, asynchronous, concurrent, and parallel interaction. They use a
fiber-based concurrency model, with built-in support for scheduling, fine-grained
interruption, structured concurrency, and high scalability.
To run an Effect value, you need a Runtime, which is a type that is capable
of executing Effect values.
The Effect interface defines a value that lazily describes a workflow or job.
The workflow requires some context R, and may fail with an error of type E,
or succeed with a value of type A.
Effect values model resourceful interaction with the outside world, including
synchronous, asynchronous, concurrent, and parallel interaction. They use a
fiber-based concurrency model, with built-in support for scheduling, fine-grained
interruption, structured concurrency, and high scalability.
To run an Effect value, you need a Runtime, which is a type that is capable
of executing Effect values.
@since ― 2.0.0
@since ― 2.0.0
Effect<
interfaceEntry
Entry,
classDatabaseError
DatabaseError>
50
readonly
deleteEntry: (entry:Entry) => Effect.Effect<void>
deleteEntry: (
entry: Entry
entry:
interfaceEntry
Entry) =>
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect.
interfaceEffect<outA, outE=never, outR=never>
The Effect interface defines a value that lazily describes a workflow or job.
The workflow requires some context R, and may fail with an error of type E,
or succeed with a value of type A.
Effect values model resourceful interaction with the outside world, including
synchronous, asynchronous, concurrent, and parallel interaction. They use a
fiber-based concurrency model, with built-in support for scheduling, fine-grained
interruption, structured concurrency, and high scalability.
To run an Effect value, you need a Runtime, which is a type that is capable
of executing Effect values.
@since ― 2.0.0
@since ― 2.0.0
Effect<void>
51
}
52
>() {}
53
54
// Create a bucket, and define the release function that deletes the
This function constructs a scoped resource from an acquire and releaseEffect value.
If the acquireEffect value successfully completes execution, then the
releaseEffect value will be added to the finalizers associated with the
scope of this Effect value, and it is guaranteed to be run when the scope
is closed.
The acquire and releaseEffect values will be run uninterruptibly.
Additionally, the releaseEffect value may depend on the Exit value
specified when the scope is closed.
@param ― acquire - The Effect value that acquires the resource.
@param ― release - The Effect value that releases the resource.
@returns ― A new Effect value that represents the scoped resource.
This function constructs a scoped resource from an acquire and releaseEffect value.
If the acquireEffect value successfully completes execution, then the
releaseEffect value will be added to the finalizers associated with the
scope of this Effect value, and it is guaranteed to be run when the scope
is closed.
The acquire and releaseEffect values will be run uninterruptibly.
Additionally, the releaseEffect value may depend on the Exit value
specified when the scope is closed.
@param ― acquire - The Effect value that acquires the resource.
@param ― release - The Effect value that releases the resource.
@returns ― A new Effect value that represents the scoped resource.
This function constructs a scoped resource from an acquire and releaseEffect value.
If the acquireEffect value successfully completes execution, then the
releaseEffect value will be added to the finalizers associated with the
scope of this Effect value, and it is guaranteed to be run when the scope
is closed.
The acquire and releaseEffect values will be run uninterruptibly.
Additionally, the releaseEffect value may depend on the Exit value
specified when the scope is closed.
@param ― acquire - The Effect value that acquires the resource.
@param ― release - The Effect value that releases the resource.
@returns ― A new Effect value that represents the scoped resource.
Scopes all resources used in this workflow to the lifetime of the workflow,
ensuring that their finalizers are run as soon as this workflow completes
execution, whether by success, failure, or interruption.
The console module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
A global console instance configured to write to process.stdout and
process.stderr. The global console can be used without importing the node:console module.
Warning: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O for
more information.
Example using the global console:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(newError('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
constname='Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console class:
constout=getStreamSomehow();
consterr=getStreamSomehow();
constmyConsole=new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(newError('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
Prints to stdout with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()).
Creates an Effect that represents a recoverable error.
This Effect does not succeed but instead fails with the provided error. The
failure can be of any type, and will propagate through the effect pipeline
unless handled.
Use this function when you want to explicitly signal an error in an Effect
computation. The failed effect can later be handled with functions like
catchAll
or
catchTag
.
@example
import { Effect } from "effect"
// Example of creating a failed effect
const failedEffect = Effect.fail("Something went wrong")
// Handle the failure
failedEffect.pipe(
Effect.catchAll((error) => Effect.succeed(Recovered from: ${error})),
Effect.runPromise
).then(console.log)
// Output: "Recovered from: Something went wrong"
The console module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
A global console instance configured to write to process.stdout and
process.stderr. The global console can be used without importing the node:console module.
Warning: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O for
more information.
Example using the global console:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(newError('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
constname='Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console class:
constout=getStreamSomehow();
consterr=getStreamSomehow();
constmyConsole=new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(newError('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
Prints to stdout with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()).
Creates an Effect that represents a recoverable error.
This Effect does not succeed but instead fails with the provided error. The
failure can be of any type, and will propagate through the effect pipeline
unless handled.
Use this function when you want to explicitly signal an error in an Effect
computation. The failed effect can later be handled with functions like
catchAll
or
catchTag
.
@example
import { Effect } from "effect"
// Example of creating a failed effect
const failedEffect = Effect.fail("Something went wrong")
// Handle the failure
failedEffect.pipe(
Effect.catchAll((error) => Effect.succeed(Recovered from: ${error})),
Effect.runPromise
).then(console.log)
// Output: "Recovered from: Something went wrong"
The console module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
A global console instance configured to write to process.stdout and
process.stderr. The global console can be used without importing the node:console module.
Warning: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O for
more information.
Example using the global console:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(newError('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
constname='Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console class:
constout=getStreamSomehow();
consterr=getStreamSomehow();
constmyConsole=new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(newError('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
Prints to stdout with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()).
Creates an Effect that represents a recoverable error.
This Effect does not succeed but instead fails with the provided error. The
failure can be of any type, and will propagate through the effect pipeline
unless handled.
Use this function when you want to explicitly signal an error in an Effect
computation. The failed effect can later be handled with functions like
catchAll
or
catchTag
.
@example
import { Effect } from "effect"
// Example of creating a failed effect
const failedEffect = Effect.fail("Something went wrong")
// Handle the failure
failedEffect.pipe(
Effect.catchAll((error) => Effect.succeed(Recovered from: ${error})),
Effect.runPromise
).then(console.log)
// Output: "Recovered from: Something went wrong"
Executes an effect and returns a Promise that resolves with the result.
Use runPromise when working with asynchronous effects and you need to integrate with code that uses Promises.
If the effect fails, the returned Promise will be rejected with the error.
@example
import { Effect } from "effect"
// Execute an effect and handle the result with a Promise
Effect.runPromise(Effect.succeed(1)).then(console.log) // Output: 1
// Execute a failing effect and handle the rejection
Effect.runPromise(Effect.fail("my error")).catch((error) => {
console.error("Effect failed with error:", error)
})
Returns an effect whose failure and success have been lifted into an
Either. The resulting effect cannot fail, because the failure case has
been exposed as part of the Either success case.
This method is useful for recovering from effects that may fail.
The error parameter of the returned Effect is never, since it is
guaranteed the effect does not model failure.
Attaches callbacks for the resolution and/or rejection of the Promise.
@param ― onfulfilled The callback to execute when the Promise is resolved.
@param ― onrejected The callback to execute when the Promise is rejected.
@returns ― A Promise for the completion of which ever callback is executed.
then(
var console:Console
The console module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
A global console instance configured to write to process.stdout and
process.stderr. The global console can be used without importing the node:console module.
Warning: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O for
more information.
Example using the global console:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(newError('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
constname='Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console class:
constout=getStreamSomehow();
consterr=getStreamSomehow();
constmyConsole=new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(newError('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
Prints to stdout with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()).
Let’s examine the test results for the scenario where FailureCase is set to undefined (happy path):
Terminal window
[S3] creating bucket
[ElasticSearch] creating index
[Database] creating entry for bucket <bucket.name> and index <index.id>
{
_id: "Either",
_tag: "Right",
right: {
id: "<entry.id>"
}
}
In this case, all operations succeed, and we see a successful result with right({ id: '<entry.id>' }).
Now, let’s simulate a failure in the Database:
construnnable= make.pipe(
Effect.provide(layer),
Effect.provideService(FailureCase, "Database")
)
The console output will be:
Terminal window
[S3] creating bucket
[ElasticSearch] creating index
[Database] creating entry for bucket <bucket.name> and index <index.id>
[ElasticSearch] delete index <index.id>
[S3] delete bucket <bucket.name>
{
_id: "Either",
_tag: "Left",
left: {
_tag: "DatabaseError"
}
}
You can observe that once the Database error occurs, there is a complete rollback that deletes the ElasticSearch index first and then the associated S3 bucket. The result is a failure with left(new DatabaseError()).
As expected, once the ElasticSearch index creation fails, there is a rollback that deletes the S3 bucket. The result is a failure with left(new ElasticSearchError()).