ravicious
Tips for building resilient frontend apps which use Phoenix channels for backend communication
When you develop an app which communicates with the backend over HTTP requests, handling unsuccessful requests is straightforward: you send a request and you get a response. Based on the response you can detect whether your request was valid or if the server crashed as a result of it.
With Phoenix channels, it’s a bit different. You still send a message and get a response back, but if your message crashes the channel, you don’t get the response and your only hope is the onError hook, which most probably lives in a different place than the code which sent the message.
As of now, our frontend app keeps the knowledge about the state of the channels it uses (whether the channels have been joined or if they crashed). If one of the channels crashes, the app replaces the whole screen with a big spinner which makes the user wait until the connection is reestablished.
The app has to do that, because the library for handling submitting forms is based on promises. If the message (which was a result of submitting the form) crashed the server, the promise for sending the form data will never get resolved. In this case, we have to destroy the form component to reset its state (otherwise it’d be stuck at “submitting” state).
Now, I’m not particularly fond of this solution. Mainly because the onError hook is called not only when the channel crashes, but also when the connection is abruptly stopped – that happens very often, especially when you’re on mobile and you open the app in the browser and then close the browser and open it again.
In short, I’d rather have my app to deal with errors when they happen as a result of user action (like in the HTTP example) rather than having a big wooly error handler (the spinner) for the whole app. I don’t want to disrupt the user experience if the connection is temporarily dropped and I still want to precisely handle server failures, but I don’t quite know how to approach that with the available tools.
Do you have any tips on how to design frontend apps to handle such error cases? Should I change my thinking and get over the fact that the semantics of HTTP requests and Phoenix channels are vastly different? 
I already read the docs about channels and the source code of the frontend client.
Most Liked
aseigo
In such cases I tend to split these kinds of things into two pieces: the part that does the communication to the client, and the part that does the business logic.
I put each in their own process, with the idea that if there is any failure in the communication process (the channel, in this case) it must mean something went quite wrong with the communication link (e.g. the network) itself and there is not much that can be done about that. The client must always be prepared for such events, so in case of failure, that’s ok.
Then the business logic part, which is where it is more common to experience bugs and recoverable errors, is run in a process separate, and therefore isolated from, the communications part.
The business process (in your case the form processor) is then monitored by the communications process (using the process monitoring that comes bundled with OTP) so if anything goes wrong there, the communications process can report back to the client or, if appropriate, try again. Often I allow some sane number of failures such as 3 before completely giving up. This also gives you a place to log the error safely, perhaps even storing the raw request for later examination (e.g. for bug fixing purposes).
This also has several additional nice side-effects: The business logic process can operate sequentially / synchronously, and the comms process can be updating the client with ping’s to keep the connection alive even when the process takes a long while. The business logic process can bang off progress messages to the comms client which can pass them on appropriately. The communications process can handle multiple requests in parallel (by spawning more business logic processes). etc, etc.
Sooo … tl;dr -> considering splitting out your business logic from the channel itself and run it in its own process 
hubertlepicki
This is interesting. I would love to see some code / read blog post… 
outlog
when you push to the channel you will receive ‘ok’, ‘error’ or ‘timeout’ much like http, so that api call in it self is pretty similar.
callApi(apiName: string, body: any): Observable<any> {
return Observable.create((observer: Observer<string>) => {
if (this.channel) {
this.channel.push(apiName, { body: body || {} }, 5000)
.receive('ok', (msg) => { observer.next(msg); })
.receive('error', resp => {
return observer.error(resp);
})
.receive('timeout', () => {
return observer.error('timeout');
});
} else {
return observer.error('no_channel');
}
});
}
How you handle the errors is also similar and depends on the UX, for GETs I usually do auto retry on error/timeout and show non blocking UI(inline spinner or what not), but the user can still nav away (of course cancel the retrys in that case). For POSTs I show an alert that there was an error and that the user should retry.
Handling the channels should be automatic in my experience - I don’t handle onError for the channel, same for the socket. Once you are connected they should reconnect if anything happens. If I crash the channel the client just rejoins almost immediately.
It of course depends on your app, a live game or data stream - you would start to handle the channel down scenario - but for these ‘api calls’ on channels the above works fine, and is handled identically to http requests.
I usually build it so you can easily swap an api call and chose to run it over http or websockets, just imagine the above function as callhttpApi, it would look pretty much the same.
So you can use channels in a very similar way as http requests.
Of course the real fun starts when you push data from the server, but all that depends on the client implementation and how data is used/stored/rerendered etc.








