Unexpected Errors
On this page
There are situations where you may encounter unexpected errors, and you need to decide how to handle them. Effect provides functions to help you deal with such scenarios, allowing you to take appropriate actions when errors occur during the execution of your effects.
Handling Unrecoverable Errors
When you encounter an unrecoverable error, meaning an error that cannot be handled or anticipated in advance, you can use the following functions to terminate the execution of the effect:
die / dieMessage
The Effect.die
function returns an effect that dies a specified error, while Effect.dieMessage
throws a RuntimeException
with a specified text message. These functions are useful for terminating a fiber when a defect, a critical and unexpected error, is detected in the code.
Example using die
:
ts
import {Effect } from "effect"constdivide = (a : number,b : number):Effect .Effect <number> =>b === 0?Effect .die (newError ("Cannot divide by zero")):Effect .succeed (a /b )Effect .runSync (divide (1, 0)) // throws Error: Cannot divide by zero
ts
import {Effect } from "effect"constdivide = (a : number,b : number):Effect .Effect <number> =>b === 0?Effect .die (newError ("Cannot divide by zero")):Effect .succeed (a /b )Effect .runSync (divide (1, 0)) // throws Error: Cannot divide by zero
Example using dieMessage
:
ts
import {Effect } from "effect"constdivide = (a : number,b : number):Effect .Effect <number> =>b === 0 ?Effect .dieMessage ("Cannot divide by zero") :Effect .succeed (a /b )Effect .runSync (divide (1, 0)) // throws RuntimeException: Cannot divide by zero
ts
import {Effect } from "effect"constdivide = (a : number,b : number):Effect .Effect <number> =>b === 0 ?Effect .dieMessage ("Cannot divide by zero") :Effect .succeed (a /b )Effect .runSync (divide (1, 0)) // throws RuntimeException: Cannot divide by zero
orDie
The Effect.orDie
function transforms an effect's failure into a termination of the fiber, making all failures unchecked and not part of the type of the effect. It can be used to handle errors that you do not wish to recover from.
ts
import {Effect } from "effect"constdivide = (a : number,b : number):Effect .Effect <number,Error > =>b === 0?Effect .fail (newError ("Cannot divide by zero")):Effect .succeed (a /b )constprogram =Effect .orDie (divide (1, 0))Effect .runSync (program ) // throws Error: Cannot divide by zero
ts
import {Effect } from "effect"constdivide = (a : number,b : number):Effect .Effect <number,Error > =>b === 0?Effect .fail (newError ("Cannot divide by zero")):Effect .succeed (a /b )constprogram =Effect .orDie (divide (1, 0))Effect .runSync (program ) // throws Error: Cannot divide by zero
After using Effect.orDie
, the error channel type of the program
is never
, indicating that all failures are unchecked, and the effect is expected to terminate the fiber when an error occurs.
orDieWith
Similar to Effect.orDie
, the Effect.orDieWith
function transforms an effect's failure into a termination of the fiber using a specified mapping function. It allows you to customize the error message before terminating the fiber.
ts
import {Effect } from "effect"constdivide = (a : number,b : number):Effect .Effect <number,Error > =>b === 0?Effect .fail (newError ("Cannot divide by zero")):Effect .succeed (a /b )constprogram =Effect .orDieWith (divide (1, 0),(error ) => newError (`defect: ${error .message }`))Effect .runSync (program ) // throws Error: defect: Cannot divide by zero
ts
import {Effect } from "effect"constdivide = (a : number,b : number):Effect .Effect <number,Error > =>b === 0?Effect .fail (newError ("Cannot divide by zero")):Effect .succeed (a /b )constprogram =Effect .orDieWith (divide (1, 0),(error ) => newError (`defect: ${error .message }`))Effect .runSync (program ) // throws Error: defect: Cannot divide by zero
After using Effect.orDieWith
, the error channel type of the program
is never
, just like with Effect.orDie
.
Catching
Effect provides two functions that allow you to handle unexpected errors that may occur during the execution of your effects.
There is no sensible way to recover from defects. The functions we're about to discuss should be used only at the boundary between Effect and an external system, to transmit information on a defect for diagnostic or explanatory purposes.
catchAllDefect
The Effect.catchAllDefect
function allows you to recover from all defects using a provided function. Here's an example:
ts
import {Effect ,Cause ,Console } from "effect"constprogram =Effect .catchAllDefect (Effect .dieMessage ("Boom!"), // Simulating a runtime error(defect ) => {if (Cause .isRuntimeException (defect )) {returnConsole .log (`RuntimeException defect caught: ${defect .message }`)}returnConsole .log ("Unknown defect caught.")})// We get an Exit.Success because we caught all defectsEffect .runPromiseExit (program ).then (console .log )/*Output:RuntimeException defect caught: Boom!{_id: "Exit",_tag: "Success",value: undefined}*/
ts
import {Effect ,Cause ,Console } from "effect"constprogram =Effect .catchAllDefect (Effect .dieMessage ("Boom!"), // Simulating a runtime error(defect ) => {if (Cause .isRuntimeException (defect )) {returnConsole .log (`RuntimeException defect caught: ${defect .message }`)}returnConsole .log ("Unknown defect caught.")})// We get an Exit.Success because we caught all defectsEffect .runPromiseExit (program ).then (console .log )/*Output:RuntimeException defect caught: Boom!{_id: "Exit",_tag: "Success",value: undefined}*/
It's important to understand that catchAllDefect
can only handle defects, not expected errors (such as those caused by Effect.fail
) or interruptions in execution (such as when using Effect.interrupt
).
A defect refers to an error that cannot be anticipated in advance, and there is no reliable way to respond to it. As a general rule, it's recommended to let defects crash the application, as they often indicate serious issues that need to be addressed.
However, in some specific cases, such as when dealing with dynamically loaded plugins, a controlled recovery approach might be necessary. For example, if our application supports runtime loading of plugins and a defect occurs within a plugin, we may choose to log the defect and then reload only the affected plugin instead of crashing the entire application. This allows for a more resilient and uninterrupted operation of the application.
catchSomeDefect
The Effect.catchSomeDefect
function in Effect allows you to recover from specific defects using a provided partial function. Let's take a look at an example:
ts
import {Effect ,Cause ,Option ,Console } from "effect"constprogram =Effect .catchSomeDefect (Effect .dieMessage ("Boom!"), // Simulating a runtime error(defect ) => {if (Cause .isIllegalArgumentException (defect )) {returnOption .some (Console .log (`Caught an IllegalArgumentException defect: ${defect .message }`))}returnOption .none ()})// Since we are only catching IllegalArgumentException// we will get an Exit.Failure because we simulated a runtime error.Effect .runPromiseExit (program ).then (console .log )/*Output:{_id: "Exit",_tag: "Failure",cause: {_id: "Cause",_tag: "Die",defect: {_tag: "RuntimeException",message: "Boom!",[Symbol(@effect/io/Cause/errors/RuntimeException)]: Symbol(@effect/io/Cause/errors/RuntimeException),toString: [Function: toString]}}}*/
ts
import {Effect ,Cause ,Option ,Console } from "effect"constprogram =Effect .catchSomeDefect (Effect .dieMessage ("Boom!"), // Simulating a runtime error(defect ) => {if (Cause .isIllegalArgumentException (defect )) {returnOption .some (Console .log (`Caught an IllegalArgumentException defect: ${defect .message }`))}returnOption .none ()})// Since we are only catching IllegalArgumentException// we will get an Exit.Failure because we simulated a runtime error.Effect .runPromiseExit (program ).then (console .log )/*Output:{_id: "Exit",_tag: "Failure",cause: {_id: "Cause",_tag: "Die",defect: {_tag: "RuntimeException",message: "Boom!",[Symbol(@effect/io/Cause/errors/RuntimeException)]: Symbol(@effect/io/Cause/errors/RuntimeException),toString: [Function: toString]}}}*/
It's important to understand that catchSomeDefect
can only handle defects, not expected errors (such as those caused by Effect.fail
) or interruptions in execution (such as when using Effect.interrupt
).