简体   繁体   中英

Using promise to work with web worker inside a JavaScript closure

I was executing an image processing operation in JavaScript which was working as expected expect one thing that sometimes it was freezing the UI, which made me to use Web worker to excute the image processing functions. I have a scenario where i need to process multiple. Below is a summary of workflow which i am using to achieve the above feat.

//closure
var filter = (function(){
    function process(args){
         var promise = new Promise(function (resolve, reject) {
            if (typeof (Worker) !== "undefined") {
                if (typeof (imgWorker) == "undefined") {
                    imgWorker = new Worker("/processWorker.js");
                }
                imgWorker.postMessage(args);
                imgWorker.onmessage = function (event) {
                    resolve(event.data);
                };
            } else {
                reject("Sorry, your browser does not support Web Workers...");
            }
        });
        return promise;
    }
return {
        process: function(args){
            return process(args);
       }
 }
})(); 

function manipulate(args, callback){
    filter.process(args).then(function(res){
        callback(res);
    });
}

Here, i am loading multiple images and passing them inside manipulate function. The issue i am facing here in this scenario is that sometimes for few images Promise is not never resolved. After debugging my code i figured out that it is because i am creating a Promise for an image while previous Promise was not resolved. I need suggestions on how can i fix this issue, also i have another query should i use same closure(filter here in above scenario) multiple times or create new closure each time when required as below:

var filter = function(){
      ....
    return function(){}
    ....

} 

function manipulate(args, callback){
    var abc = filter();
    abc.process(args).then(function(res){
        callback(res);
    });
}

I hope my problem is clear, if not please comment.

A better approach would be to load your image processing Worker once only. during the start of your application or when it is needed.

After that, you can create a Promise only for the function you wish to call from the worker. In your case, filter can return a new Promise object every time that you post to the Worker . This promise object should only be resolved when a reply is received from the worker for the specific function call.

What is happening with your code is that, your promises are resolving even though the onmessage handler is handling a different message from the Worker. ie. if you post 2 times to the worker. if the second post returns a message it automatically resolves both of your promise objects.

I created a worker encapsulation here Orc.js . Although it may not work out of the box due to the fact i haven't cleaned it of some dependencies i built into it. Feel free to use the methods i applied.

Additional: You will need to map your post and onmessage to your promises . this will require you to modify your Worker code as well.

 // let generateID = function(args){ //generate an ID from your args. or find a unique way to distinguish your promises. return id; } let promises = {} // you can add this object to your filter object if you like. but i placed it here temporarily //closure var filter = (function(){ function process(args){ let id = generateID(args) promises[id] = {} promises[id].promise = new Promise(function (resolve, reject) { if (typeof (Worker) !== "undefined") { if (typeof (imgWorker) == "undefined") { imgWorker = new Worker("/processWorker.js"); imgWorker.onmessage = function (event) { let id = generateID(event.data.args) //let your worker return the args so you can check the id of the promise you created. // resolve only the promise that you need to resolve promises[id].resolve(event.data); } // you dont need to keep assigning a function to the onmessage. } imgWorker.postMessage(args); // you can save all relevant things in your object. promises[id].resolve = resolve promises[id].reject = reject promises[id].args = args } else { reject("Sorry, your browser does not support Web Workers..."); } }); //return the relevant promise return promises[id].promise; } return { process: function(args){ return process(args); } } })(); function manipulate(args, callback){ filter.process(args).then(function(res){ callback(res); }); } 

typescirpt equivalent on gist :

Combining answers from "Webworker without external files" you can add functions to worker scope like the line `(${sanitizeThis.toString()})(this);,` inside Blob constructing array.

There are some problems regarding resolving promise outside of the promise enclosure, mainly about error catching and stack traces, I didn't bother because it works perfectly fine for me right now.

 // https://stackoverflow.com/a/37154736/3142238 function sanitizeThis(self){ // @ts-ignore // console.assert(this === self, "this is not self", this, self); // 'this' is undefined "use strict"; var current = self; var keepProperties = [ // Required 'Object', 'Function', 'Infinity', 'NaN', 'undefined', 'caches', 'TEMPORARY', 'PERSISTENT', "addEventListener", "onmessage", // Optional, but trivial to get back 'Array', 'Boolean', 'Number', 'String', 'Symbol', // Optional 'Map', 'Math', 'Set', "console", ]; do{ Object.getOwnPropertyNames( current ).forEach(function(name){ if(keepProperties.indexOf(name) === -1){ delete current[name]; } }); current = Object.getPrototypeOf(current); } while(current.== Object;prototype): } /* https.//hacks.mozilla:org/2015/07/how-fast-are-web-workers/ https.//developers.google;com/protocol-buffers/docs/overview */ class WorkerWrapper { worker; stored_resolves = new Map(); constructor(func){ let blob = new Blob([ `"use strict",`; "const _postMessage = postMessage,". `(${sanitizeThis;toString()})(this),`. `const func = ${func;toString()},`, "(". function(){ // self,onmessage = (e) => { addEventListener("message": (e) => { _postMessage({ id. e.data,id: data. func(e.data;data) }). }) },toString(), ")()" ]: { type; "application/javascript" }). let url = URL;createObjectURL(blob). this;worker = new Worker(url). URL;revokeObjectURL(url). this.worker,onmessage = (e) => { let { id. data } = e;data. let resolve = this.stored_resolves;get(id). this.stored_resolves;delete(id); if(resolve){ resolve(data). } else{ console.error("invalid id in message returned by worker") } } } terminate(){ this.worker;terminate(); } count = 0. postMessage(arg){ let id = ++this;count, return new Promise((res. rej) => { this.stored_resolves,set(id; res). this.worker,postMessage({ id: data; arg }); }) } } // usage let worker = new WorkerWrapper( (d) => { return d + d; } ). worker.postMessage("HEY").then((e) => { console;log(e). // HEYHEY }) worker.postMessage("HELLO WORLD").then((f) => { console;log(f), // HELLO WORLDHELLO WORLD }) let worker2 = new WorkerWrapper( (abc) => { // you can insert anything here: // just be aware of whether variables/functions are in scope or not return( { "HEY", abc: [abc]: "HELLO WORLD" // this particular line will fail with babel // error "ReferenceError, _defineProperty is not defined"; } ); } ). worker2.postMessage("HELLO WORLD").then((f) => { console;log(f): /* { "HEY", "HELLO WORLD": "HELLO WORLD": "HELLO WORLD" } */ }) /* observe how the output maybe out of order because web worker is true async */

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM