Select to view content in your preferred language

Accessing the JS API via a web worker

7975
28
05-15-2017 02:51 PM
RobertHewlett
Regular Contributor

I would like to offload some of the heavy lifting in a web app to a web worker.

It is a 2-3 second jank I would like gone.

Right now the web worker would still need access to parts of the API e.g. Extent and Polygon.

I have tried loading the init.js script via importScripts() but I still get errors.

Has anyone had any success with the ArcGIS JS-API inside a web worker ... not outside but inside. 

0 Kudos
28 Replies
KristianEkenes
Esri Regular Contributor

I should have clarified. I didn't require esri/Graphic. I passed graphics from the main thread to the worker and was able to keep everything, do some proximity analysis with geometryEngine, add/modify attributes, and send them back.

0 Kudos
RobertHewlett
Regular Contributor

That jives with what I am seeing.

I can send a feature set over but to make a new one inside of the worker and send that back seems to be hitting a current limit. FeatureSets have Graphics and a Graphic has Symbology ... that seems to be the issue.  

I might have to make a custom-reduced feature set (mimic everything but symbol) then stringify.

It would be great if FeatureSet could be added as many of the tasks and queries sent them back.

Is there any way to gradually update the API doc with a "Worker compatible flag"?

0 Kudos
RobertHewlett
Regular Contributor

Actually, a Simple fill symbol was fine:

"esri/symbols/SimpleFillSymbol"

The culprit seems to be Graphic or some dependency of Graphic

define("require exports ./core/tsSupport/declareExtendsHelper ./core/tsSupport/decorateHelper ./core/accessorSupport/decorators dojo/_base/lang ./core/lang ./core/JSONSupport ./PopupTemplate ./geometry/support/jsonUtils ./symbols/support/jsonUtils".split(" "), function (d, t, h, f, e, k, l, m, n, p, q) {
  ‍‍

The above line is out of the API.

0 Kudos
RobertHewlett
Regular Contributor

In the sample for the framework there is a curios line that causes an error:

esriConfig.workers.loaderConfig

0 Kudos
JohnGrayson
Esri Alum

This allows you to resolve paths to your worker scripts relative to your app so you don't have to provide a full url when calling connection.open(...).

config | API Reference | ArcGIS API for JavaScript 4.3 

0 Kudos
RobertHewlett
Regular Contributor

Actual progress (rabbit-hole-time)

Replace

The esriConfig.workers.loaderConfig stuff in the API sample code with the code below:

var local = window.location.href.substring(0, window.location.href.lastIndexOf('/'));
workers.open(this, local + "/<myJSdirectory>/<nameOfMyworkscript.js>")
.then(function(connection) {
return connection.invoke("max", {
numbers: [0, 1, 2, 3, 4]
});
})

That seems to bridge the sample code and gets the value 4 in the console.

0 Kudos
RobertHewlett
Regular Contributor

I was able to make an extent in the worker and send it back.

On my first attempt I sent the raw object back to the UI but the object seemed confused.

 

Then I used JSON.stringify() and got something that made sense.

See below:

{"xmin":1,"ymin":1,"xmax":10,"ymax":10,"spatialReference":{"wkid":4326}}

Inside the worker (BTW I flatten my code i.e. avoid fx nesting at all costs): 

define(["esri/core/promiseUtils", "esri/geometry/geometryEngine", "esri/geometry/Extent", "esri/geometry/Polygon"], work);

function work(promiseUtils, ge, Extent, Polygon) {

....

env = new Extent( 1, 1, 10, 10);
return promiseUtils.resolve({data: JSON.stringify(env)});

.....

}

JohnGrayson
Esri Alum

No need to convert it to a string, but you do need to convert the JS object to JSON:

return promiseUtils.resolve({data: env.toJSON()});

You can then recreate the Extent in your main thread by passing in the JSON directly to the Extent constructor:

RobertHewlett
Regular Contributor

That worked!

0 Kudos
ReneRubalcava
Honored Contributor

I just updated this gist to handle worker communication for things like progress updates

index.html · GitHub 

Turns out you can pass an object as the first argument to workers.open(). You can then invoke any functions from the worker on this argument.

// client
workers.open({
  progress: (msg) => {
    console.log("progress:", msg);
    return promiseUtils.resolve(msg);
  }
 }, workerUrl)

// worker
const Request = function() {}
 Request.prototype.magic = function magic({ extent }, { proxy }) {
  proxy.connection.invoke("progress", "starting");
  const area = Polygon.fromExtent(Extent.fromJSON(extent));
  proxy.connection.invoke("progress", "polygon created");
  const centroid = area.centroid;
  proxy.connection.invoke("progress", "centroid found");
  const ptBuff = geometryEngine.buffer(centroid, 1000, "feet");
  proxy.connection.invoke("progress", "buffer created");
  const data = ptBuff.toJSON();
  proxy.connection.invoke("progress", "buffer serialized");
  return promiseUtils.resolve({ data });
 }
 return Request;