Quote“when will TS be able to tracethrows
and show you what errors a function might throw
this feature would also help the whole ecosystem to throw properly typed errors”— Dax (@thdxr)source
Rapid fire questions:
- Why use Effect-ts?: Because I want to treat errors as values and nicely handle them.
- Is it functional programming mambo jambo?: I write my code using Effect-ts in an imperative way. It's inspired by functional libraries, it gives you all the tools to write in a functional way, but it's not dogmatic. If anything, sometimes using Classes is more convenient.
- Do I have to rewrite everything?: No, you can scope it to a part of your app, or even a single function. I purposely avoid using it everywhere and keep it to the parts of my app that make sense for it.
- Is it hard to learn?: It's not easy, but it's not hard. It depends on how deep you want to go. The documentation is good, and the community is very helpful.
Alright, let's dive in.
Error handling
Let's take a look at this example:
It's a big one. I'm not going to "Hello World" you. I know I'm mixing concerns; some might say I should split it into smaller functions, pull an ORM, or do things differently. But I'm not here to talk about that.
Chances are that generators aside, you can read it just fine. There's some syntax sugar, but it's familiar.
- I log some stuff
- I check if the User can create an invitation (throws
ForbiddenActionError
) otherwise - I generate a UUID (throws
UUIDGenerationError
, impossible to happen IRL, but maybe my version ofuuid
is broken) - I delete any previous invitations (throws
DatabaseError
, could be much more refined) - I check if the User is already a member (throws
InviteeAlreadyMemberError
) - I check if the Org exists (throws
OrgNotFoundError
) - unlikely to happen - I insert the invitation (throws
DatabaseError
) - I parse the invitation (throws
MembershipInvitationParse
) to the types that the frontend understands - I return the invitation
If I hover over execute
, I see this function signature (you might have to scroll a bit):
The above means that my function has the following properties:
- Returns a
MembershipInvitation
- Can error with
InternalServerError
,ForbiddenActionError
,InviteeAlreadyMemberError
,OrgNotFoundError
- No dependencies (
never
as the last type parameter. Let's discuss that in a second)
For me, this is a big deal. I can see at a glance what my function does and what can go wrong. I don't have to read the implementation to identify the errors. More importantly, I don't have to be defensive when using this function. I have a clear contract.
In the example, even though my other functions throw UUIDGenerationError
and MembershipInvitationParse
, I don't find these errors meaningful for the consumer. So I catch them and group them under InternalServerError
. The rest can go through. The consumer then can decide on the appropriate server response based on what happened.
ForbiddenActionError
-> 403InviteeAlreadyMemberError
-> 409OrgNotFoundError
-> 404 (or even a full redirect to/login
)InternalServerError
-> 500
Now, let's take a closer look at one of these errors. Specifically the InternalServerError
. I defined it as a class and can pass a reason
and metadata
to it. Nothing will reach the consumer; they are meant to be logged and reported.
Schema
As you move further into Effect-ts, you might notice that it also makes various other libraries obsolete. One of them is zod
which I can replace with Schema
. Let's take a look at my User
class:
Again too much code, but I want to show you the Schema
part. I define the schema for my User
class, and then I can use it to parse a database record or an unknown object.
Just like that...
If something odd happens, I get a UserParseError
, and I can handle it accordingly.
You might noticed that I use Brand
to create a new type for my UserId
. It is a convenient feature, and I blogged about it here. Essentially, I never want to confuse a UserId
with an OrgId
or an AnnouncementId
. They are all UUIDs, but they are identifiers of different resources.
Let's take a look at a password module. I use bcrypt
to hash and compare passwords and Schema
to validate them.
And this is where I stop
I mentioned that Effect-ts has a lot of features (Queues , PubSub , Scheduling , Streams , Dependency Injection, etc)
Here's the deal. I showed you some of the parts that I use. For me, they work nicely, and they solve my problems. But I'm certain that I'm not doing things the conventional way. Here's an example:
Remember this type signature?
I said the last type parameter is a union of the dependencies, and in this case, it's empty, just never
. I'm using a database, and a query builder (specifically the Zapatos library), so why is it not listed? Effect-ts offers a way to do dependency injection, and it's nice. But I prefer not to do it that way. I pass the pool
and db
as arguments to my function instead.
I'm trying to figure out how to put this; I want to use Effect-ts but not to be tied to it. While I was trying to understand and use the library, I found myself in a rabbit hole. I was always searching for how to do it the right way. And this led me to look at other people's snippets and repos, and I became overwhelmed. I didn't understand half of it, and I always thought I was doing everything wrong.
Instead of using a library, I spent my time trying to learn a new framework, refactoring code, and rewiring my brain. My side project was on hold, and I was not productive. (Oh, the horror)
I decided to use Effect-ts for validation and error handling. Essentially write the annoying code that can be modeled more efficiently with Effect, and use runPromiseExit
to get the result. I'm losing a lot of the library's power, but I also reduce the surface of the things I have to learn and maintain.
If things don't work out, I can keep the Error classes, replace the validation library, and remove the yield*
and Effect.gen
, and I'm back where I started. I won't have invested that much.
Don't get me wrong, I can only speak highly of the Effect and am grateful for the maintainers. If anything, I hope it will get more traction, as it's a fantastic piece of software. If my team was using Effect, I would 100% use it to its full potential and I would be singing a different song.
Quote“i love @EffectTS_ it's the exact mental model i want
but regrettably i'll never put it in a serious application
libraries that make your code look non-standard always ends up creating pain down the road
hardest part about js is convincing devs to use fewer tools”— Dax (@thdxr)source
Community
Effect has a vibrant community; you can find them on Discord.
I have only good things to say. I have asked a ton of stupid questions and always got a response. I even had feature requests that were implemented in a matter of days.
There are also great people tweeting about it, so you can follow them and get some insights. Here are some of them:
Resources