All communication to web servers is already asynchronous... Because they are using a computer network. The client makes a request, it goes to the network, and eventually, a response arrives.
You can have asynchronous code be multithreaded, and you can have it single threaded. You could convert something synchronous into something asynchronous by using continuations, coroutines, or a second thread. Usually accessing an external system is asynchronous, being the main example anything across the network. However, it is also possible to access permanent storage asynchronously, and similarly inter-process communication. Furthermore, if you need timers, it also makes sense to make them asynchronous (otherwise, you just have a thread sleep).
Furthermore, if you are using a second thread, you can convert something asynchronous into something synchronous by having a thread block until a response is ready.
Why don't we do that for the network? If we were talking of a multithreaded environment, we could afford to have one block on the network. Right? Well, we don't do that because we want to Keep the UI responsive, that means that the UI thread will keep running, and... wait, it needs to talk with the network thread... and it will do it asynchronously.
So, you can design your microservices as synchronous. However, due to the network, they will eventually be asynchronous, usually without any extra effort on the microservices part.
Finally, if the user is expecting a response, do not block the browser... make the browser put a up something that says please wait, loading, or something like that, and when the browser receives the response it can take it out and display the response.
Note: Promises on JavaScript are based on continuations. On the other hand the async/await
pattern converts the code into continuations. With these and similar tools, writing asynchronous code is easier.
Should you use WebSockets? Not necessarily. You can perfectly implement this over Ajax Fetch on a traditional Web API. I would only recommend WebSockets if the server needs to initiate an action (for example if the client needs to receive notifications). However, if everything the server sends is in response to a request of the client, then there is no need for that.
Note: If you need a quick notification of something being received over the web... first remember that if it cannot reach your server, it will have neat error HTTP status, and second, if you insist, the server can send partial responses. So ti can say HTTP status 200, but keep the connection open for more data until it is available.
By the way, you can implement continuations over the network. Evidently you cannot send a function over the network, however, you can send and id of a function... so, if the application allows you to – for example – write a product review regardless of whatever or not you are logged in, and when the user press submit, it checks and sends the user to log in... you can store submitting the product review in a continuation, so that after the user logged in, the product review can be submitted.
For more on that idea, see Defunctionalize the continuation (The Best Refactoring You've Never Heard Of)