locked
How to wrap a HTML5 Worker inside a Promise?

    Question

  • I have this worker code:

    generateCodeAsync = function(text) {
      return new WinJS.Promise(function(complete, error, progress) {
        if (!worker) {
          worker = new Worker("/js/worker.js");
          worker.onmessage = function(event) {
            complete();
          }
        }
      });
    }
    
    
    

    Calling this several times, will only call the correct complete() function on the first call. I assume its not captured right the second time. 

    So I wonder, if I should create the worker always new inside the function or can I keep my current caching of the worker?

    Whats the general recommendation? Keep the worker around or create a new one always?

    Tuesday, June 19, 2012 4:02 PM

Answers

All replies

  • Hi Phil,

    A couple notes here. Promises can only be completed once, so calling complete() multiple times in a promise doesn't do anything after the first time. In the above I notice your worker.onmessage function is capturing "complete" in a closure, so it makes sense if you use that worker again it will still call the same complete again unless you change the onmessage function.

    If I understand your use case correctly, you are trying to do asynchronous work on a WebWorker and then use promises to send the result of that work back to your main logic. I think what would make your life simpler is if you created a work queue around your WebWorker. That way you can just pull off one item at a time and message it to the WebWorker, then complete the promise in the queue when the WebWorker messages back.

    I find it convenient to use a "Deferred" style pattern for promises when doing work like this. For example:

    function getDeferred() {
      var deferred = {};
      deferred.promise = new WinJS.Promise(function(complete, error, progress) {
        deferred.complete = complete;
        deferred.error = error;
        deferred.progress = progress;
      }
      return deferred;
    }
    
    var foo = getDeferred();
    foo.promise.then(function (message) {
        console.log(message);
    });
    foo.complete("Hello World!");

    In this simple example we have a "deferred" object we can complete later by calling the "complete" function directly. This way you can just store the deferred object in the queue with your work item for the WebWorker and have the queue complete the promise by calling complete on it.

    Hopefully that helps!

    Cheers,

    -Jeff


    Tuesday, June 19, 2012 6:00 PM
  • Thanks Jeff! I tried sending a progress function off to the worker in postMessage but I got a DOM clone data error. 

    So with your deferred object I would postMessage the deferred object to the worker?

    Tuesday, June 19, 2012 6:17 PM
  • So you can't send a function reference to a worker (only things that are allowed under the Structured Clone Algorithm -http://www.w3.org/TR/html5/common-dom-interfaces.html#safe-passing-of-structured-data ). Since a worker is its own thread it can't directly call functions on the main thread. Instead you have to create your own protocol for correlating worker messages with work items. If you're just having a worker do one item at a time this can be done in memory, otherwise you could assign some unique identifier (string/number) to each request to the worker and use that instead.

    Does this make sense?

    Cheers,

    -Jeff

    Tuesday, June 19, 2012 6:24 PM
  • Yes it makes sense :)

    Or I could just spawn a new worker each time inside my Async function and worker.terminate it when its done and call the complete method. Works like a charm now.

    Tuesday, June 19, 2012 7:04 PM