Effect
Effect is a powerful TypeScript library designed to help developers easily create complex, synchronous, and asynchronous programs.
Links, Courses and Tutorials about Effect.
Terminal
npm install effect
Also make sure to use it with Typescript and use the strict flag:
tsconfig.json
{ "compilerOptions": { "strict": true }}
Why Effect?
To fully understand the following code samples, we need to understand the problem that Effect solves. Let's take a look at a simple example:
bad.ts
const divide = (a: number, b: number): number => { if (b === 0) { throw new Error('Cannot divide by zero') } return a / b}const result = divide(10, 0) // runtime error!
The following code can be problematic for various reasons:
- From the signature of
divide
, it is not clear that it can throw an error - Hence, if the error is not anticipated, it might not be handled, which can crash your whole app
What if we could specify that
divide
:- Either yields a number
- Or throws an error
Effect allows you to do just that:
good.ts
import { Effect } from 'effect'const divide = (a: number, b: number) => { if (b === 0) { return Effect.fail(new Error('Cannot divide by zero')) } return Effect.succeed(a / b)}// Effect.Effect<never, Error, number> (either yields a number or throws an error)const program = divide(10, 0) // no error hereconst result = Effect.runSyncExit(program) // no error here eitherExit.match(result, { onSuccess: (value) => console.log(`Success! ${value}`), onFailure: (cause) => console.error(`Error! ${cause}`), // error here, but gracefully handled})
You might think now: "Isn't this overkill for a simple divide?". And I agree with you: Yes it might be. Effect really comes to shine when you want your code to be typesafe and concise, and despite requiring more set-up, it scales really well when your code becomes complex.
Scheduling
scheduling.ts
import { Console, Effect, Schedule } from 'effect'const action = Console.log('success')const policy = Schedule.addDelay( Schedule.recurs(2), // Repeat for a maximum of 2 times () => '100 millis' // Add a delay of 100 milliseconds between repetitions)const program = Effect.repeat(action, policy)Effect.runPromise(program).then((n) => console.log(`repetitions: ${n}`))/*Output:successsuccesssuccessrepetitions: 2*/
Retrying
retrying.ts
import { Effect, Schedule } from 'effect'let count = 0// Simulates an effect with possible failuresexport const effect = Effect.async<never, Error, string>((resume) => { if (count <= 2) { count++ console.log('failure') resume(Effect.fail(new Error())) } else { console.log('success') resume(Effect.succeed('yay!')) }})// Define a repetition policy using a fixed delay between retriesconst policy = Schedule.fixed('100 millis')const repeated = Effect.retry(effect, policy)Effect.runPromise(repeated).then(console.log)/*Output:failurefailurefailuresuccessyay!*/
Accumulating
accumulating.ts
import { Effect } from 'effect'// Effect<never, string[], number[]>const program = Effect.validateAll([1, 2, 3, 4, 5], (n) => { if (n < 4) { return Effect.succeed(n) } else { return Effect.fail(`${n} is not less that 4`) }})Effect.runPromise(program).then(console.log, console.error)/*Output:{ _id: "FiberFailure", cause: { _id: "Cause", _tag: "Fail", failure: [ "4 is not less that 4", "5 is not less that 4" ] }}*/