Search code examples
web-workerdeno

How to get child worker send message to parent in Deno with multiple workers?


I'm new on workers, so base on this question I was trying the following:

// main.t
for(let idx = 0; idx < 4; idx++){
  const worker = new Worker(new URL('./worker.ts', import.meta.url).href, {
    type: "module", 
    deno: { namespace: true},
  });

  worker.postMessage({ id: idx, name: 'text1', email: 'ea@da.com' });
  worker.addEventListener('message', message => {
    console.log('response', message.data)
  })
}

// worker.ts
self.onmessage = async (params) => {
  const data = params.data
  console.log('params', data)
  data.name = 'text2'
  self.postMessage(data)
  self.close()
}

Expecting this:

params { id: 2, name: "text1", email: "ea@da.com" }
response { id: 2, name: "text2", email: "ea@da.com" }
params { id: 0, name: "text1", email: "ea@da.com" }
response { id: 0, name: "text2", email: "ea@da.com" }
params { id: 1, name: "text1", email: "ea@da.com" }
response { id: 1, name: "text2", email: "ea@da.com" }
params { id: 3, name: "text1", email: "ea@da.com" }
response { id: 3, name: "text2", email: "ea@da.com" }

But instead what I'm getting is:

params { id: 0, name: "text1", email: "ea@da.com" }
params { id: 1, name: "text1", email: "ea@da.com" }
params { id: 2, name: "text1", email: "ea@da.com" }
params { id: 3, name: "text1", email: "ea@da.com" }
response { id: 3, name: "text2", email: "ea@da.com" }

Can someone help me with where I'm going wrong? Why I'm only getting responses from the last worker instead of getting a response from every worker?


Solution

  • Can someone help me with where I'm going wrong? Why I'm only getting responses from the last worker instead of getting a response from every worker?

    I suspect that it's because you're closing each of the workers before postMessage can complete. In your worker.ts:

    self.postMessage(data)
    self.close()
    

    From the MDN page DedicatedWorkerGlobalScope.close():

    The close() method of the DedicatedWorkerGlobalScope interface discards any tasks queued in the DedicatedWorkerGlobalScope's event loop, effectively closing this particular scope.

    Below, I've included a refactor of your example with a bit of added type safety and more verbose logging so you can see more events and some timing info. In the example, instead of calling self.close() from within each worker, Worker.terminate() is called in the main scope's message event handler after receiving each message (after we're done with the worker). You can ignore the triple-slash directives (although they might help with type checking in your editor).

    util.ts:

    export type ExampleData = {
      sender: string;
      timestamp: number;
    };
    
    export function formatMessage (
      direction: 'in' | 'out',
      callerName: string,
      interlocutorName: string,
      timestamp: number,
    ): string {
      // 7 === Math.max('receive'.length, 'post'.length);
      const directionString = (direction === 'in' ? 'receive' : 'post').padEnd(7);
      // 8 === Math.max('worker n'.length, 'main'.length);
      return `${(callerName).padEnd(8)}  ${directionString}  ${(interlocutorName).padEnd(8)}  ${timestamp}`;
    }
    

    worker.ts:

    /// <reference no-default-lib="true" />
    /// <reference lib="deno.worker" />
    
    import {ExampleData, formatMessage} from './util.ts';
    
    function handleMessageEvent (ev: MessageEvent<ExampleData>): void {
      console.log(formatMessage('in', self.name, ev.data.sender, ev.data.timestamp));
    
      const data: ExampleData = {sender: self.name, timestamp: Date.now()};
      self.postMessage(data);
      console.log(formatMessage('out', self.name, ev.data.sender, data.timestamp));
    }
    
    self.addEventListener('message', handleMessageEvent);
    

    main.ts:

    // /// <reference lib="deno.unstable" />
    
    import {ExampleData, formatMessage} from './util.ts';
    
    function handleMessageEvent (this: Worker, ev: MessageEvent<ExampleData>): void {
      console.log(formatMessage('in', 'main', ev.data.sender, ev.data.timestamp), '✅');
      this.terminate();
    }
    
    // initialize static data once outside the loop
    const specifier = new URL('./worker.ts', import.meta.url).href;
    const options: WorkerOptions = {
      type: 'module',
      // deno: {namespace: true}, // requires deno run --unstable
    };
    
    for (let idx = 0; idx < 4; idx += 1) {
      const workerName = `worker ${idx}`;
      const worker = new Worker(specifier, {...options, name: workerName});
    
      // in testing this example, registering the event listener after posting
      // the first message didn't seem to chnage behavior, however it's safer to
      // register before posting in order to ensure that registation happens
      // prior to the event generated by the response
      worker.addEventListener('message', handleMessageEvent);
    
      const data: ExampleData = {sender: 'main', timestamp: Date.now()};
      worker.postMessage(data);
      console.log(formatMessage('out', 'main', workerName, data.timestamp));
    }
    

    Run example:

    /home/jesse/worker-example$ deno run --allow-read=. main.ts
    Check file:///home/jesse/worker-example/main.ts
    main      post     worker 0  1627390801087
    Check file:///home/jesse/worker-example/worker.ts
    main      post     worker 1  1627390801104
    Check file:///home/jesse/worker-example/worker.ts
    main      post     worker 2  1627390801122
    Check file:///home/jesse/worker-example/worker.ts
    main      post     worker 3  1627390801144
    Check file:///home/jesse/worker-example/worker.ts
    worker 0  receive  main      1627390801087
    worker 0  post     main      1627390801776
    main      receive  worker 0  1627390801776 ✅
    worker 1  receive  main      1627390801104
    main      receive  worker 1  1627390801811 ✅
    worker 1  post     main      1627390801811
    worker 3  receive  main      1627390801144
    main      receive  worker 3  1627390801873 ✅
    worker 3  post     main      1627390801873
    worker 2  receive  main      1627390801122
    worker 2  post     main      1627390801875
    main      receive  worker 2  1627390801875 ✅