Web Worker and Dart 2

I might found a reliably debug-able way to develop Dart web application with Web Worker (which is written in Dart in the same project).

This is a continued adventure from my Web Worker + Dart #1. This story is about how I work around the caveats. To recall. The first caveat is unable to develop the web application with webdev serve , because worker will not start-up correctly in dartdevc. Second caveat is force to use different method to convert the incoming data (in the worker, from the worker).

Cleaner method to talk with Worker — via Stream

The first thing I did when I revisit Web Worker with Dart is to refactor my previous code to be “Dart”ier. Communication with Worker is simple and clean, you post it message one by one, you listen to its message one by one. This is how it looks: An app communicate to a service inside a Web Worker.

This translate very well to Stream. I created a class, calling it Dorker (I am bad at naming), which has 2 StreamController that provide a Stream for event listening and a Sink to pass in message.

Next, I create 2 specialized classes, DorkerWorker and DorkerBoss, which extend Dorker. DorkerWorker will encapsulate the communication with a Web Worker, DorkerBoss will encapsulate the communication inside a Web Worker. Now: same App and service, now they talk to dorker.

Instead of using the Worker directly in the main, we will communicate with DorkerWorker. Vice versa, instead of calling raw JS interoped functions inside the Web Worker, we use the DorkerBoss.

Such extra layers?!

Before I proceed to tell how these 2 extra layers going to solve my initial problem, let discuss the other goods they bring.

  1. This decoupled the App and the service from the platform/native API. They listen to Stream and add to Sink, very Dart.

  2. Great for testing, just mock the Dorker.

  3. Is the App talking to a Web Worker or remote service? Is the service behind a worker or still in the main thread? They don’t need to know.

From all these benefits, surely you might see my solution to use dartdevc?

To dartdevc, is to not use the Web Worker

The irony… Anyway, this is my only solution to the first caveat mentioned earlier. I am a lone coder tackling a large web application development. I prefer all codes are within range (meaning, they are all in the same project) and fast turn around time (meaning, I don’t hop around different project changing 2 lines to fix a thing).

With Docker and discipline, we should have decent amount of confident to decide: with or without Web Worker in between, the application should behave exactly the same, just maybe faster or slower.

The trick is easy. Instead of using DockerWorker and DockerBoss, we use 2 base Dorker and cross link them. Using a Dart’s named constructor:

*Dorker*.*CrossLink*(*Dorker* rekrod) 
{  
  rekrod.outgoing.stream.listen(incoming.add);
  outgoing.stream.listen(rekrod.incoming.add);
}

dartdevc Mode: App and service both in main thread.

To pull this off, we need different logic between dartdevc and dart2js builds.

Use build.yaml to alternate the type of Dorker

Adding a build.yaml will let us alter the build process, a.k.a webdev serve -r. There is not much alteration we need, in fact, just one.

targets:
  $default:
    builders:
      build_web_compilers|entrypoint:
        options:
          dart2js_args:
            - -DUSE_WORKER=true

We pass in an environment variable, USE_WORKER. Remember to add -minify, -fast-startup and other good stuff for release build when you’re ready for production.

Some where in our main application, we add logic to switch between 2 modes.

Code in main is basically done above, for Web Worker script, it should be like below:

Personally, I like how lean the Worker code is, in Dart.

Keep it simple: pass only String

This is more akin to a compromise rather than deliberate choice. DorkerWorker or DorkerBoss do not enforce this, but I strongly recommend to pass only String. Reasons/Scenario/Exemption:

  • Dart object is opaque to js, and anything passing through Worker is passing through with js. We can opt for interop js object like what I did in previous post, but that require different handling in different context.

  • Even we agreed on json.encoded all objects to string, but thinking to send them over in a list (which is supported in some dart:html native function). This is not the case for Worker.postMessage. I believe this is a bug, but I’ve yet free enough to create a repro and submit the bug.

  • Exemption: pass around a js native object, e.g, File.

Dorker, a temporary bridge and a good practice

It is all started when I really wanted to use Web Worker but webdev serve just not let it. I’m pretty sure this will be look at and resolve by the Dart team soon. Mean time, Dorker can bridge that gap.

Nevertheless, Dorker turns out to be a good practice on its own. Writing a module which only takes message and pass on message via a Dorker , and now we can put this module anywhere it seems fit. In a Worker? Just simply a composition? A serverless function out in the cloud?

Please, if you have the time, check out the GitHub project. Play with it. Or leave a comment and lets discuss about this.

GitHub: https://github.com/yuan-kuan/dorker

Kuan

Kota Kinabalu, Malaysia
Email me

Usually types codes. A decade in games development, now trying to learn web.