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.
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.
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"?
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.
In the sample for the framework there is a curios line that causes an error:
esriConfig.workers.loaderConfig
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(...).
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.
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)});
.....
}
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:
That worked!
I just updated this gist to handle worker communication for things like progress updates
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;