I need some help before I lose what shred of sanity I have left. Let me start out by saying that I am pretty new to JavaScript and dojo. I am trying to rewrite our JS maps to AMD and have hit a wall when it comes to converting a DeferredList to the new dojo/promise API.
Not to overwhelm you with code, I'll just paste a snippet of the "old" code (I am aware of converting to array.map etc. but just want to show what I'm starting with).
var tasks = dojo.map(layers, function(layer) {
return new esri.tasks.IdentifyTask(layer.url);
});
//map each visible dynamic layer to a new identify task, using the layer url
var defTasks = dojo.map(tasks, function (task) {
return new dojo.Deferred();
});
//map each identify task to a new dojo.Deferred
var dlTasks = new dojo.DeferredList(defTasks); //And use all of these Deferreds in a DeferredList
dlTasks.then(showIDResults); //chain showResults nto your DeferredList ***PROBLEM HERE****
idParams.width = map.width;
idParams.height = map.height;
idParams.geometry = evt.mapPoint;
idParams.mapExtent = map.extent;
alert(tasks.length);
for (i=0;i<tasks.length;i++) { //Use 'for' instead of 'for...in' so you can sync tasks with defTasks
try {
tasks[i].execute(idParams, defTasks[i].callback, defTasks[i].errback); //Execute each task
}
catch (e) {
console.log("Error caught");
console.log(e);
defTasks[i].errback(e); //If you get an error for any task, execute the errback
}
}
The showIDResults function expects an array of results from execution of the tasks. This is were I'm having a problem. No matter what I try (and I've tried a lot of things) I end up with a Deferred or a Promise rather than actual results to pass to showIDResults.
Am I making any sense? Any help would be greatly appreciated!
Solved! Go to Solution.
In this code, I'm creating IdentifyTasks for all the visible layers (which come from different services) on my map.
function mapClickHandler(evt) { map.graphics.clear(); layerResultsGraphic.clear(); var idPoint = evt.mapPoint; var layers = array.map(map.layerIds, function (layerId) { return map.getLayer(layerId); }); layers = array.filter(layers, function (layer) { if (layer.visibleLayers !== undefined) { if (layer.visibleLayers[0] !== -1) { return layer.getImageUrl && layer.visible; } } }); var identifyParamsList = []; array.forEach(layers, function (layer) { var idParams = new IdentifyParameters(); idParams.width = map.width; idParams.height = map.height; idParams.geometry = evt.mapPoint; idParams.mapExtent = map.extent; idParams.layerOption = IdentifyParameters.LAYER_OPTION_VISIBLE; idParams.layerIds = layer.visibleLayers; idParams.tolerance = 3; idParams.returnGeometry = true; idParams.spatialReference = map.spatialReference; identifyParamsList.push(idParams); }); var tasks = array.map(layers, function (layer) { return new IdentifyTask(layer.url); }); var defTasks = array.map(tasks, function (task) { return new Deferred(); }); var promises = []; for (var i = 0; i < tasks.length; i++) { promises.push(tasks.execute(identifyParamsList)); } var allPromises = new all(promises); allPromises.then(function (r) { showIdentifyResults(r, tasks, idPoint); }); }
Here's an example from some code I have using dojo/promise/all (dojo/promise/all replacing deferredlist). I trimmed out unnecessary code.
Note that the deferreds have a 'promise' property you pass to 'all'.
//Execute 3 query tasks. store into 3 separate deferreds var pointDeferred = pointQueryTask.execute(query); var segmentDeferred = segmentQueryTask.execute(query); var textDeferred = textQueryTask.execute(query); // pass the an array of promises to all all([pointDeferred.promise, segmentDeferred.promise, textDeferred.promise]).then(function(results){ // results are returned as an array with the same order as the promises were passed var points = results[0].features; var segments = results[1].features; var text = results[2].features; console.log(results); }, function(err){ console.log("error"); });
In this code, I'm creating IdentifyTasks for all the visible layers (which come from different services) on my map.
function mapClickHandler(evt) { map.graphics.clear(); layerResultsGraphic.clear(); var idPoint = evt.mapPoint; var layers = array.map(map.layerIds, function (layerId) { return map.getLayer(layerId); }); layers = array.filter(layers, function (layer) { if (layer.visibleLayers !== undefined) { if (layer.visibleLayers[0] !== -1) { return layer.getImageUrl && layer.visible; } } }); var identifyParamsList = []; array.forEach(layers, function (layer) { var idParams = new IdentifyParameters(); idParams.width = map.width; idParams.height = map.height; idParams.geometry = evt.mapPoint; idParams.mapExtent = map.extent; idParams.layerOption = IdentifyParameters.LAYER_OPTION_VISIBLE; idParams.layerIds = layer.visibleLayers; idParams.tolerance = 3; idParams.returnGeometry = true; idParams.spatialReference = map.spatialReference; identifyParamsList.push(idParams); }); var tasks = array.map(layers, function (layer) { return new IdentifyTask(layer.url); }); var defTasks = array.map(tasks, function (task) { return new Deferred(); }); var promises = []; for (var i = 0; i < tasks.length; i++) { promises.push(tasks.execute(identifyParamsList)); } var allPromises = new all(promises); allPromises.then(function (r) { showIdentifyResults(r, tasks, idPoint); }); }
It works! It works! IT WORKS!!!!! Ken, I can't thank you enough!!!! Matthew, thank you as well; your information will be valuable to me in other aspects but Ken is doing exactly what I am trying to do.
I have struggled with this for days! May I ask you both, what is your source of knowledge on this?
I agree with John Grayson that this can be a slow operation, particularly if many layers are visible. However, this is what was required by our clients to examine all the features found at a point. Plus, I wanted to find out which service each result came from.
I had the help of Esri support to figure this out.
Alana,
performing an Identify operation on many layers can be a costly operation and not something we'd generally recommend, you might want to take a step back and figure out if there's a better way to do what you need. Regardless, here's a different way to aggregate many Identify calls using AMD style coding, this is NOT the exact code, just a guideline. Also, you might have to modify 'showIDResults' as the response structure might be different.
// What about sublayer ids? Each layer might have different sublayer ids... idParams.width = map.width; idParams.height = map.height; idParams.geometry = evt.mapPoint; idParams.mapExtent = map.extent; // arrayUtils = require("dojo/_base/array") var executeDeferreds = arrayUtils.map(layers, function(layer) { // assumes each layer supports IdentifyTask var identifyTask = new IdentifyTask(layer.url); return identifyTask.execute(idParams); }); // all = require("dojo/promise/all") all(executeDeferreds).then(showIDResults);
Thank you, John. I can see where this would be a costly operation but it is something we require. Fortunately, we are doing it on a limited number of layers.
I've tried code very similar to yours to no avail but I did get Ken's working. I may take a second look at yours as it is so similar to what I was doing and I hate being defeated.
EDIT:
?? - GeoNet was showing this with no answers until after I posted this. Not sure why but hope this helps anyway.
Check the source code in this sample.
Basically the sample takes a map click event and then creates 4 identify tasks 200m in each direction from the map click point.
... // Create multiple identify target points pts = []; pts.push(event.mapPoint.offset(0, 200)); pts.push(event.mapPoint.offset(200, 0)); pts.push(event.mapPoint.offset(0, -200)); pts.push(event.mapPoint.offset(-200, 0)); console.log(pts); // Create multiple identify tasks var tasks = []; for (var i=0;i<pts.length;i++){ tasks.push(singleIdentifyTask(pts)) } ... // this returns a single task (deferred) function singleIdentifyTask(pt){ console.log("Creating Identify Task: ", pt); identifyParams.geometry = pt; return identifyTask.execute(identifyParams); }
The code then runs the tasks within dojo/promise/all and processes the results when all tasks are completed:
// Run tasks console.log("Running Tasks"); all(tasks).then(function(taskResults){ map.graphics.clear(); for (var i=0;i<pts.length;i++){ map.graphics.add(new Graphic(pts, sms)); } // results will be an Array console.log("Results", taskResults); // example: add each result to map for (var i=0;i<taskResults.length;i++){ var taskResult = taskResults; for (var j=0;j<taskResult.length;j++){ console.log(taskResult); map.graphics.add(new Graphic(taskResult.feature.geometry, sfs)); } } // show identify points on map for (var i=0;i<pts.length;i++){ map.graphics.add(new Graphic(pts, sms)); } });
I have placed console.log statements so you can open a developer console and view the objects as they are being created or used.
Hope this helps.
Owen - SpatialXP