FredrikSandell / angular-workers

MIT License
69 stars 25 forks source link

How to Reuse or Destroy a Worker Thread #9

Closed julian-iFactory closed 8 years ago

julian-iFactory commented 8 years ago

Firstly, thanks for this great module, it is working well. I am successfully using it to retrieve comments over $http.

My only issue is that It appears to be spawning a new thread every time I request the comments for a different node (different url value in $http request) in my app . See image below from Chrome Inspector:

image

Question:

  1. How would I reuse an existing worker? A code example would be great.
  2. How do I destroy a worker?
  3. Is this a problem? Does the browser care if many idle threads are spawned?

    My Code Partial

var workerPromise = WorkerService.createAngularWorker(['input', 'output', '$http', 
                function(input, output, $http) {
                    var XSRFToken = input.XSRFToken;
                    var url = input.url;
                    var commentsModel = $http({
                        method: "get",
                        url: url,
                        headers : { 'X-XSRF-TOKEN' : XSRFToken }
                    });
                    commentsModel.then(function(response) {                        
                        output.resolve(response.data);                       
                    }, function(error) {                        
                        output.reject(error);                       
                    });
                }
            ]);
FredrikSandell commented 8 years ago

Hi Julian,

Hos are you invoking the worker run method? It is the run method that executes the codes specified in the create call. Running the create method several times would create a separate worker each time. The readme has a section on hos to invoke the rum method.

FredrikSandell commented 8 years ago

That was only a responsen to 1. Will have to get back to yoy regarding 2 and 3

FredrikSandell commented 8 years ago

Regarding 2. There should actuallly be a terminate() method available on the worker that is resolved in the promise returned from the create method. Calling that method will destroy the worker and free up the aśsociated resources. Regarding 3: I suspect this depends on the browser implementation. But in general it is a good idea to reuse web workers:)

julian-iFactory commented 8 years ago

Thanks @FredrikSandell for your fast response. I can see the .run method in the source but I can't figure out how you would reuse it once created. For example:

From my code above I create a new worker:

var workerPromise = WorkerService.createAngularWorker(['input', 'output', '$http', 
                function(input, output, $http) { *** worker function contents*** } ])

How would I call the .run method on the existing worker with new input? For example:

workerPromise.run(input)

Any help would be appreciated.

FredrikSandell commented 8 years ago

You can only call the run method on the angular-worker once it is fully initialized. That can only be done when the promise has resolved. The create method does not actually output a worker, but a promise of a worker. This is to allow the worker to be initialized before being used. Some information on the angular version of promises can be found here: https://docs.angularjs.org/api/ng/service/$q An example of how it is to be used can be found in the readme:

When the workerPromise resolves the worker is initialized with it's own angular context and is ready to use. Like so:

workerPromise.then(function success(angularWorker) {  
    //The input must be serializable  
    return angularWorker.run(inputObject);    
  }, function error(reason) {  
    //for some reason the worker failed to initialize  
    //not all browsers support the HTML5 tech that is required, see below.  
  }).then(function success(result) {  
    //handle result  
  }, function error(reason) {  
    //handle error  
  }, function notify(update) {  
    //handle update  
  });
julian-iFactory commented 8 years ago

Thanks Fredrik all working now. Sorry, I missed the angularWorker.run(inputObject) in your ReadMe.

I thought I would document my approach to reusing and terminating workers for others who might face the same problem. Below is a simplified (and untested) version of my factory that makes a $http requests for comments. This code is to address the problem of multiple threads being spawned in the browser if the factory is called many times in the one browser session.

Reusing Workers

To avoid creating a new thread in the browser on every request.

  1. Create an Angular Service to store the worker.
.service('workerModel', function() {
        this.comments = null; // initialize with something falsy.
    });
  1. Create an Angular Factory Allowing Worker Reuse
.factory('commentFactory', function($http, $timeout, $q, WorkerService, workerModel) {
    var obj = {}; // master object to return in .factory
    //------------    
    // Get Comments with Web Worker
    obj.getComments = function (input) {        
        // Detect if a worker has already been created
        // If exists use instead of creating new worker.
        var commentsWorker; // Our worker object to be created from new or existing worker.
        if (workerModel.comments){
            commentsWorker = workerModel.comments.run(input);
        } else {
            var workerPromise = WorkerService.createAngularWorker(['input', 'output', '$http',
                function(input, output, $http) {
                var getComments = $http({method: "get", url: input.url, headers : { 'X-XSRF-TOKEN' : input.XSRFToken }});
                getComments.then(function(response) {                   
                    output.resolve(response.data); 
                }, function(error) {                    
                    output.reject(error);                    
                }); 
                }
            ]);

            // Listen for Initialization of worker, Store worker then Run worker
            commentsWorker = workerPromise.then(function success(angularWorker) { //handle input success
                workerModel.comments = angularWorker; // Store angularWorker in our .service for reuse.
                return workerModel.comments.run(input);
            }, function error(reason) { //handle input error
                return reason;
            });
        }

        // Create promise so comments can be resolved in controllers/directives using this service.
        var comModel = $q.defer();

        // Listen for result of worker output .run()
        commentsWorker.then(function success(result) { //handle result    
            comModel.resolve(result);            
            return result;
        }, function error(reason) { //handle error           
            comModel.reject(reason);
            return reason;
        }, function notify(update) { //handle update            
            return update;
        });
        return comModel.promise;
        };
    //-----------
    return obj;
});
  1. Use commentFactory In your controller/directive/service ensure your add commentFactory as a dependency.
$scope.comments = commentFactory.getComments(input);

Terminate Worker After Single Use

A simpler option is to simply terminate the worker after it has returned and create a new worker on every request to the .factory

.factory('commentFactory', function($http, $timeout, $q, WorkerService, workerModel) {
    var obj = {}; // master object to return in .factory
    //------------
    // Get Comments with Web Worker
    obj.getComments = function (input) {
        var workerPromise = WorkerService.createAngularWorker(['input', 'output', '$http', 
            function(input, output, $http ) {
                var getComments = $http({method: "get", url: input.url, headers : { 'X-XSRF-TOKEN' : input.XSRFToken }});
                getComments.then(function(response) {                   
                    output.resolve(response.data); 
                }, function(error) {                    
                    output.reject(error);                    
                }); 
            }
        ]);

        // Listen for Initialization of worker then Run worker
        var commentsWorker = workerPromise.then(function success(angularWorker) { //handle input success
            workerModel.comments = angularWorker; // Store angularWorker in our .service for reuse.
            return workerModel.comments.run(input);
        }, function error(reason) { //handle input error
            return reason;
        });

        // Create promise so comments can be resolved in controllers/directives using this service.
        var comModel = $q.defer();

        // Listen for result of worker output .run() then Terminate worker.
        commentsWorker.then(function success(result) { //handle result
            comModel.resolve(result);
            $timeout(function(){
                workerModel.comments.terminate(); // Terminate Worker and thread. Requires access to angular worker. Using service from previous example to do this.
            }, 100);
            return result;
        }, function error(reason) { //handle error
            comModel.reject(reason);
            return reason;
        }, function notify(update) { //handle update
            return update;
        });
        return comModel.promise;
    };
    //-----------
    return obj;
});