I need to get better at extracting error messages for both returning the UI what actually happened, as well as logging so our monitoring can have a better idea of what failed.
Right now, most of the errors from Jzon are a weird shape, but readable in both CloudWatch and ElasticSearch if you just shove 'em to pino/console.log as is using a generic type. However, when the Lambda is complete and has an error (all my Lambdas are just 1 big Promise that returns a JSON object decoded by Jzon, or the raw error in the catch), the error is unreadable. I guess itâs using Js.String.make under the covers because itâs [object Object] which rhymes with âbloody uselessâ. Iâve seen some of the exception unpacking in the docs and asked about it in the past, but I never seem to know what Iâm supposed to be pattern matching on. Like, all my Lambdas have a custom exception up top:
exception MyCustomError(string)
And pattern matching on that works, but Promises are these mysterious things that can expose other errors like Jzon parsing failure, or Result.Error from some operation that was shoved in a Promise.resolve, or some runtime JavaScript exception from my bindings. I donât need a âsuper powerful epic pattern matcher for all errors, evarrrrâ, but rather, just a function that can be like âgimme an error, hereâs the ?.message propertyâ. Basically, ANYTHING is better than [object Object].
⌠do I have to know the types and put in them in a switch with _ at the bottom and just deal?
Hmm, yeah for that you need to know what kind of exceptions are being thrown in your system and pattern match on that to allow proper message extraction. If those exceptions would be Js.Exn.t (basically plain JS errors) then you could proceed with a pattern like this:
->catch(e => {
switch e {
| JsError(obj) =>
switch Js.Exn.message(obj) {
| Some(msg) => Js.log("Some JS error msg: " ++ msg)
| None => Js.log("Must be some non-error value")
}
| _ => Js.log("Some unknown error")
}
resolve()
// Outputs: Some JS error msg: Some JS error
})
Not sure if I understand your problem correctly though.
No, lol. Coming from JavaScript, you just define 1 catch, and no matter how big your Promise grows, how many thens, how many nested promises, you just have error handling in 1 place, log it, and all is well in the world.
ReScript: âWhat do you mean âerrorâ? Please go in all those places and find the types; the compiler will help you ensure you got 'em allâ
⌠thatâs not easy work. I wish like âall functionsâ in my Promise could use the same exception or agree to only use Result.error, that would make this a lot easier. Ok, will attempt to find then, thank you!
In ReScript you can also have one single catch in an arbitrarily complex Promise chain. At some point youâd still need to find out if youâve thrown a ReScript exn, or if you are handling a JS exception.
In practise you could also just generate and throw JS exceptions and just catch all of them in your single JS-exception-match-case.
On an unrelated note, I donât understand why the decoding library is throwing exceptions with result values. Whatâs up with that?
Iâm not sure I understood the question correctly, but Jzon never throws an exception. At least it is designed to never throw, otherwise, it is a bug. Instead, to signal an error it returns Result.Error with Jzon.DecodingError.t payload. Such an error indeed has a deeply nested shape for a case you want to recover properly. If all you need is to report the problem as an exception, use Jzon.DecodingError.toString:
Iâm taking a more detailed approach; I use a different promise library that lets me specify the rejection type.
I then set up my bindings so that every promise-based API specifies a different rejection type.
This does make the code difficult at times; in a chain the same error type must always be returned so a catch is required at every level to satisfy the type system. But with careful use it works around the âtop level catchâ problem and any resulting errors can be pattern matched quite easily.
It looks a bit like this (not real code, but the structure is how my app looks):
type errorA
type errorB
type errorC
type collectedErrors = | A(errorA) | B(errorB) | C(errorC)
input
->mayRejectErrorA
->catch(error => A(error))
->flatMap(v1 =>
v1
->mayRejectErrorB
->catch(error => B(error))
)
->flatMap(v2 =>
v2
->mayRejectErrorC
->catch(error => C(error))
)
So the main module basically triggers a Error("Unknown OCAML error.") in my logs which isnât the worst because I can read up on the log-stream. Whatâs the strategy here? Should I be exporting that exception from the sub-module somehow? I know how to export types, but never have exported exceptions before.
Not sure exactly what your code looks like but exporting an exception should be fairly easy, just declare it in the interface file. I suspect this will not solve the specific problem though, because itâs probably getting lost in translation between the ReScript exception which does not look like what a standard JavaScript exception does.
You may have better luck rejecting with more standard JavaScript exceptions, e.g. "XYZ happened"->Js.Exn.raiseError->reject.
I donât use Promise.reject in my application code at all. Itâs much more convenient and typesafe to always resolve result. You can also use ROP with the result type to handle all the possible errors in the end of operation