Using Webworkers in angular app (service worker ca

2019-02-12 19:09发布

问题:

I wish to run an function (in the background) using a worker. The data comes from a http request. I am using a mock calculation (e.data[0] * e.data[1] * xhrData.arr[3]) (replaced by a function returning the actual algo result) as below:

var ajax =  function() {
    var prom = new Promise(function(resolve, reject){
        if (!!XMLHttpRequest) {
            var xhttp = new XMLHttpRequest();
            xhttp.onload = function () {
                if (this.readyState == 4 && this.status == 200) {
                    resolve(JSON.parse(this.responseText));
                }
            };
       // Cache Logic - Will be adding logic to check cache 
       // if test.json is in cache.
       // If it is then fetch res from cache
       // There will be multiple XHR requests in parallel, not one
            xhttp.open("GET", "test.json", true);
            xhttp.send();
        }
    });
    return prom;
}

async function test (e) {
    var workerResult, xhrData;
   try {
    xhrData  = await ajax();
    workerResult = (e.data[0] * e.data[1] * xhrData.arr[3]);
    postMessage({res: workerResult});
   } catch(err) {
    postMessage({err: 'Failed'});
   }
}

onmessage = function (e) {
    test(e);
};

This works fine. But, this is a pure JS implementation. I was planning to use a service (plus a shared worker) for this so I create only one worker per angular app and dont have memory issues. This is going to be a trigger from a user button action of form submit.

My question:

First, I am wondering if this can be done by service workers in Angular itself since it is also a type of background worker thread.

Second, If not possible then can I access the cache of service workers from web worker? and Is it possible to access this service worker cache. How is this supposed to be done? Any help is welcome.

Note that I am able to work with service workers and I am able to cache all static assets using angular service workers.

Update:

I was able to get some basic idea of enabling data cache in the angular app using following config which I am currently working on.

{
    "name": "someapi",
    "urls": ["/someuri", "/users"],
    "cacheConfig": {
      "strategy": "freshness",
      "maxSize": 20,
      "maxAge": "1h",
      "timeout": "5s"
    }
  }

Update:

I was able to get this up and running in a crude but it worked. Added the asset that needed the XHR request into the ngsw-config.json in the assets section. This cached the request into service worker cache. The service workers cache can be opened using caches.open('ngsw:db:${name}') but I did not have to do that.

I created a web worker file inside the assets folder
The XHR request was made in it. 
When a XHR was made the service worker automatically picked up the cache
So I did not have to use any alternate methods of cache access.
Sworkers was automatically served the XHR request from the cache.

Here is how I achieved that. I created a service in angular for the service worker:

@Injectable({
  providedIn: 'root'
})
export class WebworkerService {
  myWorker: any;
  constructor() {
      this.myWorker = new Worker('/assets/web-worker.js');
      this.myWorker.onmessage = function(data) {
        console.log(data);
      }
  }

}

Then I created a web-worker.js file in the assets folder:

var ajax =  function() {
    var prom = new Promise(function(resolve, reject){
        if (!!XMLHttpRequest) {
            var xhttp = new XMLHttpRequest();
            xhttp.onload = function () {
                if (this.readyState == 4 && this.status == 200) {
                    resolve(this.responseText);
                }
            };
            xhttp.open("GET", "/assets/test.md", true);
            xhttp.send();
        }
    });
    return prom;
}

async function test (e) {
    var workerResult, xhrData;
   try {
    xhrData  = await ajax();
    workerResult = xhrData; // Some calculation or activity here
    postMessage({res: workerResult});
   } catch(err) {
    postMessage({err: 'Failed'});
   }
}

onmessage = function (e) {
    test(e);
};

My ngsw-config.json had assets section which cached assets/test.md:

{
    "name": "assets",
    "installMode": "lazy",
    "updateMode": "prefetch",
    "resources": {
      "files": [
        "/assets/**"
      ]
    }
  }

From the component, say for example, app.component.ts I triggered a postMessage()

@Component({
  selector: 'app-root',
  template:`
 <h1 (click)="myHttp()">
    Some Client Event
  </h1>
`,
  styleUrls: ['./app.component.css'],
  providers: []
})
export class AppComponent {
  constructor(private _ww: WebworkerService) { }
  myHttp() {
    this._ww.myWorker.postMessage('Test');
  }

}

This makes the web-worker.js trigger the XHR request. Though I was expecting I will have to use a cache access api my self, it was not so. The service worker automatically served the file from the cache (Which is fantastic). However if there is a need to access the cache I found this can be done using the cache API here: https://developer.mozilla.org/en-US/docs/Web/API/Cache

I am sure things can be bettered and file structuring can be made cleaner as per best practices. If you find a better solution please leave an answer so it helps everyone.

回答1:

Yes this can be done by a service worker but :

  • The browser can terminate a service worker if it thinks the service worker has finished working. So be sure to wrap everything inside a promise and use event.waitUntil to make sure that the process isn't terminated before it has finished.

    self.addEventListener("message", function(event) { event.waitUntil(test(event)); });

  • The service worker runs on a single thread, so if you do long synchronous operation you'd be slowing every request of your page. Service workers are not meant to do long calculation.

Yes shared workers can access the cache api. You can access it the same way you do from a service worker by using the global variable caches

caches.open(cacheName).then(function(cache) {
      cache.match("...")
});