Search code examples
multithreadingscalaasynchronousweb-workerscala.js

How to wrap Web Worker response messages in futures?


Please consider a scala.js application which runs in the browser and consists of a main program and a web worker.

The main thread delegates long running operations to the web worker by passing messages that contain the names of methods and the parameters required to invoke them. The worker passes method return values back to the main thread in the form of response messages.

In simpler terms, this program abstracts web worker messaging so that code in the main thread can call methods in the worker thread in idiomatic and asynchronous Scala syntax.

Because web workers do not associate messages with their responses in any way, the abstraction relies on a registry, an intermediary object, that governs each cross context method call to associate the invocation with the result. This singleton could also bind callback functions but is there a way to accomplish this with futures instead of callbacks?

How can I build an abstraction over this registry that allows programmers to use it with the standard asynchronous programming structures in Scala: futures and promises?

How should I write this functionality so that scala programmers can interact with it in the canonical way? For example:

// long running method in the web worker
val f: Future[String] = Registry.ultimateQuestion(42) // async

f onSuccess { case q => println("The ultimate question is: " + q) }

I'm new to futures and promises, but it seems like they usually complete when some execution block terminates. In this case, receiving a response from the web worker signifies completion of the future. Is there a way to write a custom future that delegates its completion status to an external process? Is there another way to link the web worker response message to the status of the future?

Can/Should I extend the Future trait? Is this possible in Scala.js? Is there a concrete class that I should extend? Is there some other way to encapsulate these cross context web worker method calls in existing asynchronous Scala functionality?

Thank you for your consideration.


Solution

  • Hmm. Just spitballing here (I haven't used workers yet), but it seems like associating the request with the Future is fairly easy in the single-threaded JavaScript world you're working in.

    Here's a hypothetical design. Say that each request/response to the worker is automatically wrapped in an Envelope; the Envelope contains a RequestId. So the send side looks something like (this is pseudo-code, but real-ish):

    def sendRequest[R](msg:Message):Future[R] = {
      val promise = Promise[R]
      val id = nextRequestId()
      val envelope = Envelope(id, msg)
      register(id, promise)
      sendToWorker(envelope)
      promise.future
    }
    

    The worker processes msg, wraps the result in another Envelope, and the result gets handled back in the main thread with something like:

    def handleResult(resultEnv:Envelope):Unit = {
      val promise = findRegistered(resultEnv.id)
      val result = resultEnv.msg
      promise.success(result)
    }
    

    That needs some filling in, and some thought about what the types like R should be, but that sort of outline would probably work decently well. If this was the JVM you'd have to worry about all sorts of race conditions, but in the single-threaded JS world it probably can be as simple as using an autoincrementing integer for the request ID, and storing away the Promise...