Interfaces between main and worker thread

We want to use TypeScript to enforce types everywhere we can. We first define some interfaces to be used for exchanges between the main thread (the Angular application in our case) and the worker threads.

A background task will wrap jobs that we want to execute on the worker thread. Background task messages will be sent back by the worker to the main thread to monitor status and progresses.

A simple worker

Let’s begin by writing a simple TypeScript worker with all the logic contained in a single class.

The worker actions

We continue to take advantage of TypeScript and enforce the prototype of the actions supported by the worker.

So the worker will implement this interface and manage its state accordingly.

One thing to note at this point: we use ES6 fat arrow notation for all class methods (so it’s not methods but functions). This is important for the following.

Note: At the transpilation, the ‘method notation’ will be added to the prototype and the ‘fat arrow’ (or function) notation will be added as a property instead. Or we will relate on the toString() function later which not support prototype content in our case.

External scripts import

One basic need in the workers is to import external Javascript scripts with the native importScripts function. We will also use the postMessage function to send messages back to the main application. We can declare theses typings as we know they will be available at the runtime.

For working with our architecture the importScripts arguments have to be a complete URL including the base location of the application (e.g. http://my.domain.org/assets/my_external_js_dependency.js )

To get the base location, and other parameters that can be useful, on the worker side, we accepts a WorkerConfig object in the constructor.

We have now a worker with some functions that we want to call, and which can post back messages that we will handle from the Angular side.

The worker service

An Angular service will take care of all the worker management, including:

The worker instantiation

Sending instructions to the worker

Receiving messages from the worker

The worker termination

The tricky part: the worker object URL creation

Firstly we create an object URL containing the worker payload. This is the trick which allows us to use our TypeScript worker without a build configuration. It uses the fact that we can stringify any Javascript symbol.

Note: this step also induces the limitations of the method

We have to write some plain text (unfortunately) to instantiate our worker class and attach the event listener.

So it is not the best part, but remember we want to inject some properties in the worker constructor. We have to do that in the plain text worker template.

The worker instantiation

When a task is started, a new worker is instantiated and all its messages will be transmitted to an Observable for easier end consumption in the application.

Note: for simplicity the demo project spawns one worker per task but there is no limitation preventing to run multiple task on the same worker (with some small adaptations).

Sending instructions to the worker

Let’s build a method notify to send instructions the the worker. We can enforce the type of the action .

When notify is called, a postMessage is performed on the worker thread and our registered event listener maps the WorkerAction to the corresponding worker function.

Receiving messages from the worker

For easier consumption, we will wrap all messages received by the worker in an Observable .

We simply attach two listeners, for message and error , on the worker and dispatch the event data to the observable. We also enforce the message type as BackgroundTaskMessage .

The start instruction is sent to the worker once the listeners have been attached.

The worker termination

When a task is over, a message with a status TERMINATED will be sent by the worker. When this message is received, the observable is completed and the worker terminated.

Usage

All is now set-up to use our worker in our application. The demo sample shows how to start and monitor some tasks executed on the worker. We can interact with the worker and receive back its messages by calling the worker service.

Appendix

Use another TypeScript class inside the worker

We are still missing one feature: use other TypeScript classes inside our worker.

If we just use another class in our main worker class, it will not be available at runtime. Remember that we just produce a string representation of one of our symbol (mainly BackgroundWorker in our example) but nobody (_Webpack_) is here to resolve our dependencies. So we have to “inject” ourselves external TypeScript symbols we want to use.

The good thing is we can enforce the types of our dependencies on the worker side. The bad thing is we will have to add some plain text in the Angular service as we did for the properties.

And now we can instantiate the classes in the worker constructor. We can pass any classes or properties with this trick. Types are enforced but we must not forget to write it in the template string (without typos ;)).

Limitations

There are some other limitations to the code written to be executed in the worker thread. It includes the worker class itself but also all “injected” dependencies.