Zaid-Ajaj / Fable.Remoting

Type-safe communication layer (RPC-style) for F# featuring Fable and .NET Apps
https://zaid-ajaj.github.io/Fable.Remoting/
MIT License
272 stars 54 forks source link

Possibility of adding a global error handler on the web client? #301

Closed enriquein closed 2 years ago

enriquein commented 2 years ago

Hello ๐Ÿ˜„. I was wondering if there could be a way to add an error handler to the web proxies just like we can have them in the server side. My specific use case for this would be that I want to perform a specific set of actions if any of the endpoints at any point return 401/UnAuthorized. At the moment I'm using React.useDeferred and React.useDeferredCallback from Feliz, so I wrote a function that does the set of actions if the ProxyRequestException has a StatusCode of 401. The issue is that in order for me to implement it, at the moment I need to place the function in each match block where I check the status of the deferred value. I worry that at some point someone in my team might forget to set the function properly, since we're used to using interceptors in other frameworks/libraries for these concerns.

Something like this maybe:

let musicStore = 
    Remoting.createApi()
    |> Remoting.withErrorHandler errorHandler  // to make the API similar/consistent with the server counterpart
    |> Remoting.buildproxy<IMusicStore>
enriquein commented 2 years ago

Decided to take a stab at this and landed on what I think is a good happy medium. Instead of a global error handler, I made it possible to add a global interceptor in PR #303. Hope this is useful to more people.

Zaid-Ajaj commented 2 years ago

Hi there @enriquein, so this problem has popped before and in fact, we did have a similar feature to what you implemented in #303 (it was years ago when we still used CEs for remoting builders),

The reason we removed was so that we do encourage users to NOT handle errors globally. An error is ideally handled at the place where it happens. That said, it is sometimes useful to have a global error handler and the current API already allows for it. Let me explain, so instead of using an instance of a protocol API, let's call that IServerApi, you WRAP the function in another function that handles global errors and exceptions:

module Api

// creata an instance of the protocol record
let serverApi = 
    Remoting.createApi()
    |> Remoting.buildproxy<IServerApi>

// wrap it here to handle global errors
let call (f: IServerApi -> Async<'t>) = 
  async {
      try
        let! result = f serverApi
        return (Some result)
      with
      | ex -> 
          // Do something global
          // show toast
          // redirect to login
          return None
  }

Now instead of Api.serverApi.funcName() you would write Api.call (fun api -> api.funcName())

Yes, a bit more code but does what you need to do in this instance. Also you could make call return a union of things you care about, not just Option<'t>.

You could also implement variants of Api.call such as Api.callWithIntercepter f intercepter where intercepter is statusCode -> responceBody -> unit and so on.

I hope this helps ๐Ÿ™

enriquein commented 2 years ago

I can't believe it never occurred to me to wrap the API ๐Ÿ˜…

Thank you for your feedback! Maybe we could add this somewhere in the docs so future generations don't reimplement this feature as well.