1

I am working on HTML5 web worker and I made a function that spawn few workers and return the result, but problem is that it returns the value before the worker updates the result. So I want to delay return statement until all results are received

for (i = 0; i < array1_rows; i++)
{
    var worker = new Worker('json.js');
    worker.postMessage(arr1[i]);
    worker.postMessage(arr2);
    worker.postMessage(i);

    worker.onmessage = storeResult;
}

/////////////////////////////////

return result;

So I just want to delay that return statement until result are received. Plz help me on how to use the yield in java script.

Purnil Soni
  • 831
  • 4
  • 18
vicky
  • 673
  • 1
  • 8
  • 14

2 Answers2

6

Like the comment points out - Web Workers work asynchronously (think of how AJAX works)

You can use an asynchronous semaphore to yield only when they're all done

function doSomething(callback){
    var counter = array1_rows;
    for (i = 0; i < array1_rows; i++)
    {
        var worker = new Worker('json.js');
        worker.postMessage(arr1[i]);
        worker.postMessage(arr2);
        worker.postMessage(i);

        worker.onmessage = function(){
            storeResult.apply(arguments);//call it the same way it was called before.
            counter--;
            if(counter === 0){ // all workers returned
                callback(result); //or whatever you're updating in storeResult
            }
        };
    }
}

Now you can call it like:

doSomething(function(result){
    console.log(result); //this will print the correct result.
});

For more information about how JS async operations work I recommend this ajax related question that includes a description of the problem and how to approach it.,

Community
  • 1
  • 1
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • 1
    but my problem remains same i dont want to print out that result i want to return that so where should i write that return statement to ensure the proper result – vicky Aug 24 '13 at 22:38
  • You can't. Workers are asynchronous, you accept a _callback_ and you execute it when the code is done. _Please_ read that AJAX question I linked to to see how asynchronous JavaScript works. – Benjamin Gruenbaum Aug 26 '13 at 13:43
  • 1
    Please, note that the attribute/method onmessage is case sensitive. This means that if you write onMessage instead of onmessage, the method will NOT be executed, so be careful. – joninx Feb 15 '16 at 08:41
1

Use Promises!

The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.


Step 1: Extend the native Worker class

// main.js

class MyWorker extends Worker {

  constructor (src) {
    super(src)
  }
  
}
// worker.js

const COMMAND_ONE = 0
const COMMAND_TWO = 1

addEventListener('message', ({data}) => {
  let result
  
  switch (data.type) {
    case COMMAND_ONE:
      // Some awesome off-main computations
      // result = ...
      break
    case COMMAND_TWO:
      // etc.
      break
    default:
      result = 'Received an empty message')
  }
  
  postMessage(result)
})

Step 2: Override postMessage()

By overriding the native postMessage() method, not only can we send messages to the worker thread, we can send the result back in a Promise. In the Worker script, no changes need to be made (yet).

// main.js

class MyWorker extends Worker {

  constructor (src) {
    super(src)
  }
  
  postMessage (message, transfer=[]) {
    return new Promise((resolve) => {
      const onmessage = ({data}) => {
        this.removeEventListener('message', onmessage)
        resolve(data)
      }
      this.addEventListener('message', onmessage)
      super.postMessage(obj, transfer)
    })
  }
  
}

Step 3: Use MessagePorts

This is not optional. Since webworkers are asynchronous with the main thread, we cannot simply assume that any of MyWorker's responses will be received in the same order of the calls to postMessage().

For correct asynchronous handling we need to guarantee that the message we received in our local onmessage arrow function is indeed a response to the respective postMessage() call (rather then any message the Worker thread happened to send back at that time).

// main.js

class MyWorker extends Worker {

  constructor (src) {
    super(src)
  }
  
  postMessage (obj, transfer=[]) {
    return new Promise((resolve) => {
      const {port1, port2} = new MessageChannel()
      transfer.push(port2)
      this._lastCall = Date.now()
      const onmessage = ({data}) => {
        port1.removeEventListener('message', onmessage)
        resolve(data)
      }
      port1.addEventListener('message', onmessage)
      super.postMessage(obj, transfer)
      port1.start()
    })
  }
  
}
// worker.js

const COMMAND_ONE = 0
const COMMAND_TWO = 1

addEventListener('message', ({data, ports}) => {
  const port = ports[0]
  let result
  
  switch (data.type) {
    case COMMAND_ONE:
      // Some awesome off-main computations
      // result = ...
      break
    case COMMAND_TWO:
      // etc.
      break
    default:
      result = 'Received an empty message')
  }
  
  // Send result back via a unique Port
  port.postMessage(result)
})

Step 4: Use async/await

Rather then just calling postMessage() (which is still valid and could be appropriate e.g. if no response is expected and the timing the Worker script has no impact on other code) we can now use async functions and await the result before we move on.

Be careful to only use await if you are sure you will receive a response from the Worker thread. Not doing so will lock the async function!

// main.js

class MyWorker {
  
  static COMMAND_ONE = 0
  static COMMAND_TWO = 1
  // etc.
  
  // ...
}

async function foo (w) {
  let value1 = await w.postMessage({type: MyWorker.COMMAND_ONE, values: ''})
  value1 += value1 + await w.postMessage({type: MyWorker.COMMAND_TWO, values: ''})
  
  let raceFor = []
  raceFor.push(w.postMessage({type: MyWorker.COMMAND_THREE, values: ''}))
  raceFor.push(w.postMessage({type: MyWorker.COMMAND_FOUR, values: ''}))
  raceFor.push(w.postMessage({type: MyWorker.COMMAND_FIVE, values: ''}))
  raceFor.push(w.postMessage({type: MyWorker.COMMAND_SIX, values: ''}))
  
  let result
  try {
    result = Promise.race(raceFor)
  }
  catch (e) {
    result = Promise.resolve('No winners')
  }
  
  return result
}

const w1 = new MyWorker('worker.js')
const res = foo(w1)

Promise - Javascript | MDN

MessageChannel - Javascript | MDN

async - Javascript | MDN