Overview
Effect is a TypeScript library with a large set of features that:
- maximizes type safety
- improves error handling
- makes code more composable, reusable, and testable
Recent Changes
Several significant changes where made to the Effect library in early 2024 which make most tutorial videos somewhat wrong.
- The order of the type parameters for the generic type
Effectchanged from<Requirements, ErrorType, SuccessType>to<SuccessType, ErrorType, Requirements>. - The schema types had changes.
- The operation of the
pipefunction changed. - The generator no longer requires wrapper functions.
Effects
An Effect is an immutable value that
describes an operation (a.k.a a program) to be run later.
It is a type that is generic over three values:
- A success type (
voidif there is no result) - An error type (
neverif no errors can occur) - An optional set of requirements that
must be fulfilled in order for the code to run
(
neverif there are none,Errortype is the most general).
The result of an Effect is only computed when it is run.
To create an Effect from a value,
use the Effect.succeed(value) or Effect.fail(error-object) functions.
There are four ways to create an Effect.
If the function to be run never throws …
- If the function is synchronous, use
Effect.sync(fn). - If the function is asynchronous and never throws, use
Effect.promise(fn).
In both cases, fn is the function to be run.
If the function can throw …
- If the function is synchronous, use
Effect.try(obj). - If the function is asynchronous, use
Effect.tryPromise(obj).
In both cases, obj is an object with try and catch properties.
The try property value is a function that can throw.
A Promise is returned.
When an Effect is run, it produces a value or an error.
It is similar to the Result type in languages like OCaml.
To run an Effect:
- If it never throws, use
effect.runSync. - If it can throw, use
Effect.runPromise.
Pipelines
Pipelines pass values through a series of functions. Each function take one value from the previous function, and produces one result that is passed to the next function in the pipeline.
A pipeline is defined by calling the Effect.pipe function
or the effect.pipe method.
The Effect.pipe function takes an initial value and a series of functions,
and passes the initial value to the first function.
The effect.pipe method takes a series of functions,
and passes the value of the Effect on which it is called
to the first function.
The value returned by a function can be transformed
with the Effect.map and Effect.flatMap functions.
Side effects can be triggered wtih the Effect.tap function.
The values from multiple effects can be combined with the Effect.all function.
Error Handling
TODO: Discuss creating objects with a _tag property
that is used to identify an error type.
To handle all kinds of errors, use the Effect.catchAll function.
To handle a specific kind of error, use the Effect.catchTag function.
To handle multiple specific kinds of errors, use the Effect.catchTags function.
TODO: Is there also an Effect.catch function?
TODO: What does Effect.provideService do?
Logging
The Effect library provides a set of functions for logging.
Effect.logDebugEffect.logInfoEffect.logWarningEffect.logErrorEffect.logFatal
By default, output from all of these is disabled.
To specify a logging level where all logging at that level and above is output,
call the Effect.withMinimumLogLevel(level) function.
To include timing information in log output,
call the Effect.withLogSpan function.
Generators
Generators can simplify Effect code in a way that is
similar to using the JavaScript async and await keywords.
This utilizes the JavaScript yield* keyword.