Fork me on GitHub

AsyncProxy Examples

PAGE IS STILL IN CONSTRUCTION

AsyncProxy is a simple helper library enables migrating a whole javascript class to a web-worker. The library supports some simple use cases which will be detailed in this page.


AsyncProxy Hello world!

The most simple use of AsyncProxy is to instantiate a class in a web worker and call its functions. For that the AsyncProxy should be provided the following data:

  • The script(s) including the required code in the Web Worker (absolute path only).
  • Class name to instantiate.
  • Class's relevant method description.

Assume we have a class named Callee in a script '/scripts/callee.js', that has a simple method helloWorld. In order to create a Proxy class for the Callee class you only need to provide the above information by using the following lines:

(One way to get an absolute path is using the getEntryUrl() function, which returns the absolute path of the source page.)

The AsyncProxy library may be added to your page by:

<script src="http://MaMazav.github.io/cdn/async-proxy.dev.js"></script>

Let's demonstrate this basic usage by the following Callee class. The below Callee class will be used also in the rest of this page. For now, notice only the helloWorld() function which may take a lot of time:

Instantiating the Proxy and calling the helloWorld() from remote is easy as:

var proxy = new AsyncProxyHelloWorld({ helloWorldCtorArgument: 10000 });
proxy.helloWorld(50000);

Instantiating the AsyncProxyHelloWorld class will spawn an underlying Web Worker and call the Callee constructor there. Then, the call to proxy.helloWorld will activate the Callee.helloWorld method on the Web Worker.

An alternative way to manage the imported scripts will be described in the section about ScriptsToImportPool class.

Another equivalent way is to define the method by custom way:

This code is equivalent to the one defined in async-proxy-hello-world, unless it is more customable. It may be useful for complex features that will be shown later.


Sub-Worker emulation for Chrome

But what if the class already uses workers internally? It should be able to spawn its sub-workers although not supported at Chrome.

Using AsyncProxy that's not a problem. The AsyncProxy library emulates sub-workers for Chrome seamlessly. Notice, however, that those workers are actually spawned from the main thread context, thus a communication overhead affects the UI also.

Particularly, the proxied class can spawn sub-workers indirectly by using AsyncProxy library by itself, as in the callSubWorker() function of the Callee class above. The SubWorkerProxy class below defines the proxy method - by the same way we defined the simple hello world function: The sub-worker is supported transparently.


Passing special arguments and results

If the arguments and method result passed to and from the Web Worker are trivial, then defining method as above by "helloWorld: [ ]" is OK. However, in several cases a special treatment is needed for the arguments to be passed to or from the Web Worker:

  • One-time callback argument - a function is not serializable. Special bookkeeping is required in order to pass callback calls from the Web Worker. Fortunately the async-proxy.js library can do that for you behind the scene. (see Custom Callback section for details about customizing callbacks for multiple calls).
  • Promise return value - Similarly to function arguments, the async-proxy.js library can simulate returning Promise by special bookkeeping.
  • Transferables - To avoid copying of heavy objects when passing arguments to Web Worker proxied function, transferables (also known as ports) can be used. Transferables are supported both in arguments and in a Promise result.
  • Change call order - Sometimes massive function calls on Web Worker may take too long and block later function calls. If the later function calls are more important, they can be marked to be called immediately to bypass the earlier calls.

AsyncProxyHelloWorldCustom class below demonstrates the above features API:

The following code demonstrate calling these functions:

The custom equivalent way to define the above methods is below:


Manual termination

Terminating the underlying Web Worker is as easy as calling to terminate() function. Notice that if you need to do some release operations on the worker side when terminating the class (e.g. release resources, send information to server, etc.), you should wait for these operations to finish before calling to terminate(). In some use-cases Promise may be useful here.

Function terminate() also closes all sub-workers (including those were spawned using Chrome sub-workers emulation as explained above).


Custom callback options

We previously demonstrated a simple callback. The callbacks require the AsyncProxy library to save bookkeeping information in the Web Worker side in order to identify the original callback. By default this bookkeeping information is released after the first call to it, as stated above. If you would like to call the callback more than once you may change this behavior by customizing it as shown below:


Slave-side features

On all the cases mentioned above we didn't write any slave-side code (a code that is executed on the Web Worker), except of the proxied class itself. One may want to inject a slave-side code without changing the slave class implementation. The features in this section allows you to do so.


Custom creation of slave side instance

Until now we've seen a simple way to control the instance created on the slave side. The creation was done by passing a string indicates the class constructor name, and arguments array. If you would like to execute some logic in the slave side before creating the instance you need an additional

The setSlaveSideCreator allows you to do that - instead of defining a class which its instance will be created, you can provide a factory function which will create your instance using any logic you would like to and actually enables to define any way you would like to instantiate the class.

The slave-side script should call the setSlaveSideCreator function:

The master-side proxy definition should indicate that the above script is needed to be imported on the slave side:


Sending user data from slave to master

Until now, the only option to send data from slave to master is by using callback arguments sent by the master or by Promises. It means that the master should initiate an action to allow the slave to send data. The sendUserDataToMaster overcomes it.

The slave only need to send the data whenever he would like to:

The master can listen and receive the sent data by setting a handler for this:


setBeforeOperationListener

The slave is able to listen events happen on its side. It is done by providing a listener function to setBeforeOperationListener method.

The this argument of the function is the slave side instance (either created automatically or by setSlaveSideCreator function). The first argument that the listener function accepts is the event type. Currently the only possible event type is 'callback', and it will be called before any time a callback is called by the slave instance. In case of callback the second argument of the listener function is the callback name as was defined by the call to wrapCallback, and the third argument is the arguments passed to the callback.

Contribution is welcome - it is easy to extends the AsyncProxy.js implementation to support more types of events.


ScriptsToImportPool class

An alternative of passing the absolute paths of each imported script is using the ScriptsToImportPool class. This approach is useful if the imported scripts are already used in the master side. You can instantiate this class and call the addScriptFromErrorWithStackTrace method from each script you would like to import. Then use the getScriptsForWorkerImport method to get the list of scripts you would like to import.

The addScriptFromErrorWithStackTrace method is implemented according to an approach described here. This approach requires an instance of Error class, contains which the stack trace when instantiating Error. Thus this function requires an argument of the Error class which was instantiated in the correct script.

Practically, to use the ScriptsToImportPool, add two lines in the file contains the proxied class implementation:

Then you can use it when implementing the proxy: