Showing posts with label Web workers. Show all posts
Showing posts with label Web workers. Show all posts

Friday, July 6, 2012

JSON: serialize and deserialize functions in JavaScript

In my Linq2IndexedDB project, I take advantage of the web workers to do all the filtering that the IndexedDB API doesn’t allow (multiple filters, like, inArray, …). For all these filters I have an “isValid” method which determines if the data satisfies the condition. So when I want to use these methods in my background worker, I need to make sure I can call them. For that I have 3 possibilities

  1. Add a copy of all the filters in my background worker file
  2. Include the file where my filters are defined
  3. Serialize and deserialize the “isValid” functions

The first one wasn’t an option for me, because i wanted to keep all filter logic at one place. The second one was an option, but I didn’t want to use multiple JavaScript files for my library. And an other reason why I don’t like the first 2 is because developers would have to change my library if they want to add additional filters. So this left me only with the third possibility.

For this I’ve done some research. I already knew that you could call .toString on a function and this would result into a string representation of the function. And with the Function.call method I would be able to call the function in my background worker. But I didn’t wanted a solution that had to call the Function.call method in the background worker, I wanted to use the .isValid method that was provided in the filter objects. So I dug into the JSON API and found the following solution.

Serialize Objects

The JSON.stringify (the method that turns JavaScript objects into JSON text) accepts 2 arguments. The first argument is the object you want to turn into a JSON text and the second argument accepts a replacer function. This function gets called for every value in the object structure (even for properties in child objects) and accepts a key (name of the property) and a value (the value of the property) argument. The return value of the object is the object that will be stringified. By using this function, I can return the string representation of the function (in case of function) and return the value in the other cases.

   1: JSON.stringify(filters, function (key, value) {
   2:     if (typeof value === 'function') {
   3:         return value.toString();
   4:     }
   5:     return value;
   6: });


deserialize objects


The next thing we want to do, is deserialize the function again. Like the stringify method, the parse method (returns a JSON string into a JavaScript objects) also a callback (reviver) method as second argument. This method also gets called for every key and value for every level of the result. In this callback you can reform generic objects into instances of pseudo classes, strings into dates, strings into functions, …



   1: JSON.parse(filtersString, function (key, value) {
   2:     // reform your objects here
   3: });

Once I have the string representation of the function, I can start rebuilding that function. This is done by creating a new Function Object. The constructor of the Function objects accepts 2 arguments, a list of arguments for the function you want to create and the string representation of the body of the function you want to create.



   1: new Function(arguments, functionBody);

So if we put those 2 together we get the following:



   1: JSON.parse(filtersString, function (key, value) {
   2:     if (value 
   3:         && typeof value === "string" 
   4:         && value.substr(0,8) == "function") {
   5:         var startBody = value.indexOf('{') + 1;
   6:         var endBody = value.lastIndexOf('}');
   7:         var startArgs = value.indexOf('(') + 1;
   8:         var endArgs = value.indexOf(')');
   9:  
  10:         return new Function(value.substring(startArgs, endArgs)
  11:                           , value.substring(startBody, endBody));
  12:     }
  13:     return value;
  14: });


Detecting if the value is a function, is done by checking if the value is a string and starts with the word ‘function’. If this is the case, we will determine the arguments and the function body so we can pass it to the Function constructor. Retrieving the arguments of the new function is done by taking the string value between the first ‘(‘ and ‘)’. Retrieving the function body is done by getting the string value between the first ‘{‘ and the last ‘}’. Passing these 2 values to the constructor of the function will create a new function. This is the value that gets returned in case of a function. In all other cases we just return the value.


Conclusion


By using the stringify and parse method of the JSON API you can provide a generic way to serialize and deserialize your objects. Serializing functions can easily be done by calling the toString method on the function and deserializing a function can be done by using the Function constructor which is present in the JavaScript language.

Friday, February 17, 2012

Web worker: running js tasks in the background

Since JavaScript is more and more used for building applications, rather than providing extra features, you need to take care you don’t freeze the UI. This is one of the reasons why the W3C introduced the web worker API. This API provides a way to run JavaScript in a thread different form the UI thread. This way long running code such as sorting large array’s, won’t freeze the the UI or make your application unresponsive. Generally, workers are expected to be long-lived, have a high startup performance cost and a high per-instance memory cost.

workers

When we want to run code in a thread different from the UI thread, then we need to put this code in separate file. This file, will then be called by the worker to run in another thread. The only thing you need to do in the in the code that will run in another thread is calling postMessage method whenever you want to send data to the UI-thread.

Continuous worker

This code will start running when ever a worker with that file is instantiated and will continue running.

Example of a file (prime.js) to run in the background:

   1: var n = 1;
   2: prime: while(true){
   3:     n += 1;
   4:     for (var i = 2; i <= Math.sqrt(n); i += 1){
   5:         if (n % i == 0){
   6:             continue prime;
   7:         }
   8:     }
   9:     // Found prime
  10:     postMessage(n)
  11: }

This code will send a message to the UI thread every time a prime is found.



   1: // Starts a new worker
   2: var worker = new Worker('prime.js');
   3: worker.onmessage = function (event){
   4:     // event.data contains a prime
   5:     document.getElementById("result").textContent = event.data
   6: }

The code above is used to create a new worker. The worker will execute the code in the prime.js file in a thread separate from the UI. In our case, the code will start calculating which numbers are primes.


The onmessage event on the worker gets triggered every time the postMessage method is called inside the worker thread. This way we can display the prime on the screen.


dedicated Worker


This worker will only start executing when you call the postMessage method on the worker.


Example of a file (sort.js) to run in the background:



   1: onmessage = function (event) {
   2:     var data = event.data.data;
   3:     var propertyName = event.data.propertyName;
   4:     var sortedData = data.sort(JSONComparer(propertyName).sort);
   5:     postMessage(sortedData);
   6:     return;
   7: };
   8:  
   9: function JSONComparer(propertyName) {
  10:     return {
  11:         sort: function (valueX, valueY) {
  12:                 return ((valueX[propertyName] == valueY[propertyName]) 
  13:                         ? 0 : ((valueX[propertyName] > valueY[propertyName]) ? 1 : -1));
  14:         }
  15:     }
  16: }

The onmessage method will handle a postMessage send to the worker. This will start the work you want to execute. In this case the code will sort an array of JSON objects on a given property. When the array and the propertyName is provided as data in the postMessage method, The data will get sorted on the property with the given propertyName. When the sort is completed, the sorted array will be send to the UI.



   1: var worker = new Worker("sort.js");
   2: var data = [{ name: "test2" }, [{ name: "test4" }, [{ name: "test1" }, [{ name: "test3" }]
   3: worker.onmessage = function (event) { /* event.data contains the sorted Array */) };
   4: worker.postMessage({ data: data, propertyName: "name"});

With the postMessage method we can provide the data that is needed to sort an array. When the sorting is completed and the postMessage method in the background thread is called, the onmessage function will be triggered in the UI thread so we can process the sorted array.


More information about workers can be found here.