I’m not all caught up on the Promise plans, but for Unhandled Promise Exceptions, I think it depends on if using type
Promise.t('a, 'e), we still have
Promise.error… or if, as discussed, somehow the exception goes up into the
'e. It’s difficult because you can
reject with a useful error value but anything below that promise can just throw at any point… so that type of
'e really needs to be something like
type t('e) = | Cause('e) | Exception(Js.Exn.t).
We also now just avoid promises entirely because
Promise.error is not very useful. We use callbacks for exceptions we actually want to track.
I know Relude and ReScript don’t have the same goals, but this is exactly what we used
Relude.Void.t for. Say you have
Promise.t('a, 'e), you handle the error with a
Promise.catch… you end up with
Promise.t('a, Void.t)… meaning the error is handled. At the “end” of your app logic when you need to go back into ffi, type it to only accept
Promise.t('a, Void.t) and you know you have no unhandled errors. Could even call it
Void.t is too scary.
Result.t<Promise.t<Result.t<'a, 'e>>, 'e>
We had this problem a few times when we first started down this path. Generally the types will just stop you, but I think it mostly has to do with how confusing it is to have to do so much manual promise wrapping and unwrapping all of the time without any real help. Working directly with Promises of Results, we ended up with like 3x more code noise… completely unhelpful, masks logic, and invites errors.
We solved this, like I mentioned above, by creating a
Zio.t type to represent it. Once we wrote helper functions specific to this type, like
Zio.flatMap (I know, I know…) this issue completely disappeared. Outside of onboarding someone new to this pattern, it never comes up. (But, also, as I wrote above, we did stop using promises entirely.)
Usually our code is something like:
10 lines of ffi (input)
1000 lines of application logic
10 lines of ffi (output)