Cause
On this page
The Effect<A, E, R>
type is polymorphic in values of type E
, which means we can work with any error type we want. However, there is additional information related to failures that is not captured by the E
value alone.
To preserve and provide comprehensive information about failures, Effect uses the Cause<E>
data type. Cause
is responsible for storing various details, such as:
- Unexpected errors or defects
- Stack and execution traces
- Causes of fiber interruptions
Effect is designed to be strict in preserving all the information related to a failure. It captures and stores the complete story of failure in the Cause
data type. This ensures that no information about the failure is lost, allowing us to precisely determine what happened during the execution of our effects.
Although we don't typically work directly with Cause
values, it is an underlying data type that represents errors occurring within an Effect workflow. It provides us with total access to all concurrent and sequential errors within our codebase. This gives us the ability to analyze and handle failures in a comprehensive manner whenever needed.
Creating Causes
We can intentionally create effects with specific causes using the Effect.failCause
constructor:
ts
import {Effect ,Cause } from "effect"// Create an effect that intentionally fails with an empty causeconsteffect =Effect .failCause (Cause .empty )
ts
import {Effect ,Cause } from "effect"// Create an effect that intentionally fails with an empty causeconsteffect =Effect .failCause (Cause .empty )
To uncover the underlying cause of an effect, we can use the Effect.cause
function:
ts
Effect.cause(effect).pipe(Effect.flatMap((cause) => ...))
ts
Effect.cause(effect).pipe(Effect.flatMap((cause) => ...))
Cause Variations
There are several causes for various errors, in this section, we will describe each of these causes.
Empty
The Empty
cause represents a lack of errors.
Fail
The Fail
cause represents a Cause
which failed with an expected error of type E
.
Die
The Die
cause represents a Cause
which failed as a result of a defect, or in other words, an unexpected error.
Interrupt
The Interrupt
cause represents failure due to Fiber
interruption, which contains the FiberId
of the interrupted Fiber
.
Sequential
The Sequential
cause represents the composition of two causes which occurred
sequentially.
For example, if we perform Effect's analog of try-finally
(i.e.
Effect.ensuring
), and both the try
and finally
blocks fail, we have two
errors which occurred sequentially. In these cases, the errors can be
represented by the Sequential
cause.
Parallel
The Parallel
cause represents the composition of two causes which occurred in parallel.
In Effect programs, it is possible that two operations may be performed in
parallel. In these cases, the Effect
workflow can fail for more than one
reason. If both computations fail, then there are actually two errors which
occurred in parallel. In these cases, the errors can be represented by the
Parallel
cause.
Guards
To identify the type of a Cause
, you can use specific guards provided by the Cause
module:
Cause.isEmpty
Cause.isFailType
Cause.isDie
Cause.isInterruptType
Cause.isSequentialType
Cause.isParallelType
Let's see an example of how you can utilize these guards:
ts
import {Cause } from "effect"constcause =Cause .fail (newError ("my message"))if (Cause .isFailType (cause )) {console .log (cause .error .message ) // Output: my message}
ts
import {Cause } from "effect"constcause =Cause .fail (newError ("my message"))if (Cause .isFailType (cause )) {console .log (cause .error .message ) // Output: my message}
By using these guards, you can effectively determine the nature of a Cause
, enabling you to handle different error scenarios appropriately in your code. Whether it's an empty cause, failure, defect, interruption, sequential composition, or parallel composition, these guards provide a clear way to identify and manage various types of errors.
Pattern Matching
In addition to using guards, you can handle different cases of a Cause
using the Cause.match
function. This function allows you to define separate callbacks for each possible case of a Cause
.
Let's explore how you can use Cause.match
:
ts
import {Cause } from "effect"constcause =Cause .parallel (Cause .fail (newError ("my fail message")),Cause .die ("my die message"))console .log (Cause .match (cause , {onEmpty : "(empty)",onFail : (error ) => `(error: ${error .message })`,onDie : (defect ) => `(defect: ${defect })`,onInterrupt : (fiberId ) => `(fiberId: ${fiberId })`,onSequential : (left ,right ) =>`(onSequential (left: ${left }) (right: ${right }))`,onParallel : (left ,right ) =>`(onParallel (left: ${left }) (right: ${right })`}))/*Output:(onParallel (left: (error: my fail message)) (right: (defect: my die message))*/
ts
import {Cause } from "effect"constcause =Cause .parallel (Cause .fail (newError ("my fail message")),Cause .die ("my die message"))console .log (Cause .match (cause , {onEmpty : "(empty)",onFail : (error ) => `(error: ${error .message })`,onDie : (defect ) => `(defect: ${defect })`,onInterrupt : (fiberId ) => `(fiberId: ${fiberId })`,onSequential : (left ,right ) =>`(onSequential (left: ${left }) (right: ${right }))`,onParallel : (left ,right ) =>`(onParallel (left: ${left }) (right: ${right })`}))/*Output:(onParallel (left: (error: my fail message)) (right: (defect: my die message))*/
Pretty Printing
When working with errors in your code, it's crucial to have clear and readable error messages for effective debugging. The Cause.pretty
function provides a convenient way to achieve this by generating nicely formatted error messages as strings.
Let's take a look at how you can use Cause.pretty
:
ts
import {Cause ,FiberId } from "effect"console .log (Cause .pretty (Cause .empty )) // All fibers interrupted without errors.console .log (Cause .pretty (Cause .fail (newError ("my fail message")))) // Error: my fail messageconsole .log (Cause .pretty (Cause .die ("my die message"))) // Error: my die messageconsole .log (Cause .pretty (Cause .interrupt (FiberId .make (1, 0)))) // All fibers interrupted without errors.console .log (Cause .pretty (Cause .sequential (Cause .fail ("fail1"),Cause .fail ("fail2"))))/*Output:Error: fail1Error: fail2*/
ts
import {Cause ,FiberId } from "effect"console .log (Cause .pretty (Cause .empty )) // All fibers interrupted without errors.console .log (Cause .pretty (Cause .fail (newError ("my fail message")))) // Error: my fail messageconsole .log (Cause .pretty (Cause .die ("my die message"))) // Error: my die messageconsole .log (Cause .pretty (Cause .interrupt (FiberId .make (1, 0)))) // All fibers interrupted without errors.console .log (Cause .pretty (Cause .sequential (Cause .fail ("fail1"),Cause .fail ("fail2"))))/*Output:Error: fail1Error: fail2*/
Retrieval of Failures and Defects
If you're specifically interested in obtaining a collection of failures or defects from a Cause
, you can use the Cause.failures
and Cause.defects
functions respectively.
Let's see how you can utilize these functions:
ts
import {Cause } from "effect"constcause =Cause .parallel (Cause .fail (newError ("my fail message 1")),Cause .sequential (Cause .die ("my die message"),Cause .fail (newError ("my fail message 2"))))console .log (Cause .failures (cause ))/*Output:{_id: 'Chunk',values: [Error: my fail message 1 ...,Error: my fail message 2 ...]}*/console .log (Cause .defects (cause ))/*Output:{ _id: 'Chunk', values: [ 'my die message' ] }*/
ts
import {Cause } from "effect"constcause =Cause .parallel (Cause .fail (newError ("my fail message 1")),Cause .sequential (Cause .die ("my die message"),Cause .fail (newError ("my fail message 2"))))console .log (Cause .failures (cause ))/*Output:{_id: 'Chunk',values: [Error: my fail message 1 ...,Error: my fail message 2 ...]}*/console .log (Cause .defects (cause ))/*Output:{ _id: 'Chunk', values: [ 'my die message' ] }*/