Thursday, January 21, 2016

I participated in a little Twitter discussion today ( and...

I participated in a little Twitter discussion today ( and was asked to write up my recommendations, so here we go:

One of the challenges of adding concurrency to web apps is that some APIs require sync execution – and a fully concurrent app, where the app runs on a different Worker thread from the UI thread, cannot directly make such sync calls.

Fortunately the number of APIs that are relevant here is pretty small:
- event.preventDefault(): E.g. if you want a tag to not actually directly go to that link. Also often needed with touch events.
-…): Because popups are blocked if they don't get opened synchronously from a click handler.
- Probably 1 or 2 more I forgot about.

At Google we've been facing this issue for years actually: We typically late load all parts of the apps, and that includes implementations for click handlers: So we might not be able to synchronously handle a click all the time – which brings us to exactly the same problem as having the app running in a different thread.

There are various solution flavors, but they all are derived from the same idea:
- is rare, just do something hacky special thing for it
- preventDefault is the more important use case:

The base strategy is to install global event handlers on the UI thread that either always call preventDefault on all events they see or do so based on a DOM annotation. If you always call preventDefault, you need to have user land implementations of all default actions (like window navigation). We typically use a DOM annotation instead:

E.g. if you have an
tag, add and then the global UI thread click handler (which might need to look at ancestors of the target), calls preventDefault on events when it sees this.

One important considerations is that while is rare, you need to use it to "implement" target=_blank for clicks on a tags that might have had preventDefault called on them. We typically handle this by putting a limited version of the app's router into the synchronous call path. That typically allows handling almost all cases of advanced preventDefault magic on the UI thread while keeping the rest of the app away from it.