Closed tmcw closed 1 month ago
There's a bit about "Customizing Error Handling", which would let me re-map errors, but what if I have a known error that I'm throwing - I guess I need to do an
instanceof
check in the catch method?
The problem is you can't have known errors with Promise's, so you have to either cast the unknown if you are certain that the Promise only rejects with a certain type of error, or perform runtime checks. Or you can just leave it as unknown and put it in a cause
of another error type.
Part of the reason Effect exists is precisely to put guards around unexpected JS behaviour. As Tim mentioned you can't infer what a Promise throws I'd say a few things:
1) Effect.promise
use case is not limited in use, you should track in the type system only Recoverable errors so for the majority of cases unexpected errors will fall under defects.
2) Effect.tryPromise
by default can't infer what was thrown, so it's rightfully an UnknownError
that you can re-map, also you can use the catch
clause to do your error mapping. See https://github.com/microsoft/TypeScript/issues/13219#issuecomment-1515037604 for a proper way to handle throws from generic thunks of code, the linked code applies to Effect as well:
Effect.tryPromise({
try: () => ...,
catch: (e) => {
if (e instanceof TypeError) {
return e
} else {
throw e;
}
}
})
3) Effect.async
is a way to bridge the gap to callback-style code, if you have a Promise using Effect.async
won't yield any advantage over Effect.tryPromise
only added verbosity, if you do have a callback-style API it is usually better to use it, for example Node's old callback based APIs had the error explicitly typed so in that case you can leverage it directly
4) Mapping external errors to domain errors is a good practice regardless, typed errors are supposed to be recoverable so it is assumed you will have control-flow over them, to simplify dealing with those the standard is to use tagged errors, so even when you don't know what happened in details you get a higher level idea, for example:
import { Data, Effect } from "effect"
class FetchError extends Data.TaggedError("FetchError")<{
cause: unknown
}> {
message = "Something wrong with fetch call"
}
// (id: number) => Effect<any, FetchError, never>
const fetchTodo = (id: number) => Effect.tryPromise({
try: () => fetch(...),
catch: (cause) => new FetchError({ cause })
})
Thanks! I think that makes more sense - having read a bunch more code that uses Effect, it seems like in cases like mine I'll want to wrap library functions that might throw exceptions into tryPromise blocks. When I 'know' what the exceptions can be thrown it's a little annoying to test their types and re-identify them in tryPromise, but I guess it's worthwhile as part of the tradeoffs.
What is the type of issue?
Documentation is confusing
What is the issue?
I'm reading through the documentation for modeling asynchronous effects, and I find the ordering and structure a little tricky at first:
The intro states:
The first documented API is
Effect.promise
, which looks promising (sorry), but only applies if you have a promise "where you're confident that the asynchronous operation will always succeed". Promise rejections are "defects" if you use Effect.promise. So it seems very limited in usefulness, and doesn't fulfill the enticing intro about how we're going to use the type system to track errors, because we can't track errors withEffect.promise
, at least as documented.Next we've got
Effect.tryPromise
. This lets us handle rejections, but then says:So, again, not really using the type system to represent types of rejections. There's a bit about "Customizing Error Handling", which would let me re-map errors, but what if I have a known error that I'm throwing - I guess I need to do an
instanceof
check in the catch method?Below this, I see the docs for handling callbacks, which look a lot like the API that I'd want, but they're described as a way to bridge the gap for callback-based code, rather than a way to do things with async.
It seems like using Effect.tryPromise, with the custom catch handler, is the way to do asynchronous computation with typed errors. But the ergonomics seem a bit weird, and it's confusing that the docs kind of hide it, despite the pitch being centered around typed errors for asynchronous work.
Where did you find it?
https://effect.website/docs/guides/essentials/creating-effects#modeling-asynchronous-effects