Writing Synchronous Wrappers for Asynchronous Methods in TeaVM
In my last post, I shared a little bit about the work I’ve been doing porting Codename One into Javascript using TeaVM. In that post I mentioned that TeaVM supports multithreading in the browser. In my opinion, this is a major advancement as it finally allows us to write code synchronously in the browser. I.e. NO MORE CALLBACK HELL!
This is great, however, all of the existing Javascript APIs work asynchronously so if we want to work with them synchronously, we need to write synchronous wrappers for them. I expect that, over time, we’ll develop a standard library of such wrappers, but for now, we may need to do some wrapping on our own.
Example: Synchronous Wrapper for XHR Requests
The following method wraps XMLHTTPRequest
to allow us to load the contents of a URL as a byte array. A similar mechanism could be used to fetch the contents as text, or a blob.
public Uint8Array getArrayBuffer(String url){
Window window = (Window)JS.getGlobal();
final XMLHttpRequest req = window.createXMLHttpRequest();
final Object lock = new Object();
final boolean[] complete = new boolean[1];
req.open("get", url, true);
req.setOnReadyStateChange(new ReadyStateChangeHandler() {
@Override
public void stateChanged() {
if ( req.getReadyState() == XMLHttpRequest.DONE ){
new Thread() {
@Override
public void run() {
complete[0]=true;
synchronized(lock){
lock.notifyAll();
}
}
}.start();
}
}
});
req.setResponseType("arraybuffer");
req.send();
while (!complete[0]){
synchronized(lock){
try {
lock.wait();
} catch (InterruptedException ex) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, ex);
}
}
}
if (req.getResponse() == null ){
System.out.println(req.getAllResponseHeaders());
System.out.println(req.getStatusText());
System.out.println("Failed to load resource "+url);
System.out.println("Status code was "+req.getStatus());
return null;
}
return window.createUint8Array(
(ArrayBuffer)req.getResponse()), req.getResponseType());
}
See a neater version of this code as a Gist
A couple of things to notice:
- Inside
stateChanged()
, we spawn a newThread
because threading primitives likesynchronized
andnotifyAll()
won’t work directly running in a native Javascript callback (at the time of this writing). - The loop with
lock.wait()
doesn’t block the main Javascript thread. It uses callbacks in the background to pause execution of this “thread” until thelock.notifyAll()
method is called.
Now we can use this method directly in our Java code to load data from a remote server synchronously without blocking the UI thread, and without introducing callback hell into our code.