user11276880
user11276880

Reputation:

How to write a Promise wrapper around Web Workers API?

I am writing a library which makes use of Web Workers. The consumer of the library should not see any of the Web Worker stuff and should instead get returned a Promise from the public methods of this library like this:

// consumer.js

const api = new Api();

api.doCalculation(input1).then(data => console.log(data));
api.doCalculation(input2).then(data => console.log(data));
api.doCalculation(input3).then(data => console.log(data));

In my library code I have a class which wraps the Web Worker logic. In the constructor I create the worker and set the "message" event listener, listening for incoming data from the worker thread. Also in this class there is a doCalculation(input) method which is public to the consumer of the library. It takes the input and sends it to the worker thread to perform the actual calculation.

// api.js

class Api {
  constructor() {
    this.worker = new Worker('worker.js');

    this.worker.addEventListener('message', (e) => {

      // I want to return this e.data from the doCalculation method
      console.log(e.data);
    });
  }

  doCalculation(input) {
    this.worker.postMessage({input: input});

    // I want to return a Promise from this method,
    // holding the result e.data
  }
}

My question now is, how can I return a Promise from the doCalculation method holding e.data?

My first intend was something like this which obviously doesn't work because a new "message" event listener is created with every call to doCalculation.

// api.js

class Api {
  constructor() {
    this.worker = new Worker('worker.js');
  }

  doCalculation(input) {
    this.worker.postMessage({input: input});
    return new Promise((resolve => {
      this.worker.addEventListener('message', (e) => {
        resolve(e.data);
      });
    }))
  }
}

All the code examples here are simplified to only make my point clear.

I would be thankful for any hints into the right direction!

Upvotes: 5

Views: 2638

Answers (1)

Jonas Wilms
Jonas Wilms

Reputation: 138297

For sure you could store resolve somewhere, e.g. in an object:

 this.resolvers = {};
 this.count = 0; // used later to generate unique ids

Then for each task sent to the webworker, create a unique id, and store the promise resolver there

 const id = this.count++;
 // Send id and task to WebWorker
 return new Promise(resolve => this.resolvers[id] = resolve);

Then when the webworker sends a message, get the id from it, and resolve the stored promise:

 this.resolvers[ id ](data);
 delete this.resolvers[id]; // Prevent memory leak

That way (1) you only need to register one handler, (2) multiple tasks can be handled at the same time by the webworker and (3) You can easily determine which tasks are running on the webworker by checking the Object.keys(this.resolvers).

Upvotes: 7

Related Questions