I'm trying to query address points from two FeatureLayer objects for one or more parcel polygons. The user will select the parcel polygons on the map, and behind the scenes, the address points will be selected. Once all the address points are selected, then I need to make an API call. I've written some code, shown below, and I'm close, but my outer "all" promise is not calling the API as expected. How do I do nested async calls like this, and have all complete before calling my API? Thanks!
define(["dojo/_base/declare", "dojo/promise/all", "dojo/_base/lang", "dojo/_base/array", "esri/tasks/query"], function (declare, all, lang, array, Query) { var sdcAddressSelector = declare(null, { constructor: function () { this.addresses = []; }, addAddressesToList: function(response) { response = array.filter(response, function (r) {//filter out any failed tasks return r.length > 0; }); array.forEach(response, function (r) { this.addresses.push(r); }); }, callAPI: function () { //api calls here }, executeSelect: function (selectedParcels) { var _this = this; var selectList = array.map(selectedParcels, function (parcel, index) { qry = new Query(); qry.geometry = parcel.feature.geometry; //async loop over the address layers to get the addresses that intersect the current parcel var addressList = array.map(esriMap.addressServices, function (layer, index) { layer.queryFeatures(qry, lang.hitch(_this, _this.addAddressesToList)); }); return all(addressList); }); return all(selectList, lang.hitch(this, this.callAPI)); }, }); return sdcAddressSelector; });
Solved! Go to Solution.
Shawn,
This is what I have working in a sample I threw together:
(I pass the maps SpatialReference in using the spatRef property of the sdcAddressSelector).
define(["dojo/_base/declare", "dojo/promise/all", "dojo/_base/lang", "dojo/_base/array", "esri/tasks/query"], function (declare, all, lang, array, Query) { var sdcAddressSelector = declare(null, { spatRef: null, addresses: null, constructor: function () { this.addresses = []; }, addAddressesToList: function (response) { response = array.filter(response, function (r) { //filter out any failed tasks return r.features.length > 0; }); array.forEach(response, lang.hitch(this, function (r) { this.addresses.push(r); })); this.callAPI(); }, callAPI: function () { //api calls here console.info(this.addresses); }, createQueryParams: function (layers, geom) { var queryParamsList = []; array.forEach(layers, lang.hitch(this, function () { var queryParams = new Query(); queryParams.geometry = geom; queryParams.outFields = ['*']; queryParams.returnGeometry = false; queryParams.outSpatialReference = this.spatRef; queryParamsList.push(queryParams); })); return queryParamsList; }, executeSelect: function (selectedParcels, esriMap) { var promises = []; array.map(selectedParcels, lang.hitch(this, function (parcel) { var params = this.createQueryParams(esriMap.addressServices, parcel.geometry); array.map(esriMap.addressServices, lang.hitch(this, function (layer, index) { promises.push(layer.queryFeatures(params[index])); })); })); var qPromises = new all(promises); qPromises.then(lang.hitch(this, function (r) { lang.hitch(this, this.addAddressesToList(r)); }), lang.hitch(this, function (err){ console.info(err); })); } }); return sdcAddressSelector; });
Try this...
define(["dojo/_base/declare", "dojo/promise/all", "dojo/_base/lang", "dojo/_base/array", "esri/tasks/query"], function (declare, all, lang, array, Query) { var sdcAddressSelector = declare(null, { constructor: function () { this.addresses = []; }, addAddressesToList: function (response) { response = array.filter(response, function (r) { //filter out any failed tasks return r.length > 0; }); array.forEach(response, function (r) { this.addresses.push(r); }); }, callAPI: function () { //api calls here }, executeSelect: function (selectedParcels) { var _this = this; var selectList = array.map(selectedParcels, function (parcel, index) { qry = new Query(); qry.geometry = parcel.feature.geometry; //async loop over the address layers to get the addresses that intersect the current parcel var addressList = array.map(esriMap.addressServices, function (layer, index) { layer.queryFeatures(qry, lang.hitch(_this, _this.addAddressesToList)); }); return addressList.promise; }); return all(selectList).then(lang.hitch(this, this.callAPI))); }, }); return sdcAddressSelector; });
Well, that didn't do it. Thanks for catching the missing ".then" in line 35, but my outer promise is returning before my internal promise. So my API call happens before I have all of the address points returned from the layer.queryFeature calls.
Did you make the change on line 31 as well?
I did, thanks.
Shawn,
Could you do something like Esri is doing within the "Manage results from multiple queries" demo?
Manage results from multiple queries | ArcGIS API for JavaScript
No, as the design is to handle n+ number of parcels selected, so a loop is required. This is the challenge. To use the example you cite, I need to run the parcel and buildings queries n+ times, and wait until ALL of those finish before I continue. If it were simply a known number of queries I wouldn't have asked the question, as I already know how to do that. Thanks anyways.
Shawn,
This is what I have working in a sample I threw together:
(I pass the maps SpatialReference in using the spatRef property of the sdcAddressSelector).
define(["dojo/_base/declare", "dojo/promise/all", "dojo/_base/lang", "dojo/_base/array", "esri/tasks/query"], function (declare, all, lang, array, Query) { var sdcAddressSelector = declare(null, { spatRef: null, addresses: null, constructor: function () { this.addresses = []; }, addAddressesToList: function (response) { response = array.filter(response, function (r) { //filter out any failed tasks return r.features.length > 0; }); array.forEach(response, lang.hitch(this, function (r) { this.addresses.push(r); })); this.callAPI(); }, callAPI: function () { //api calls here console.info(this.addresses); }, createQueryParams: function (layers, geom) { var queryParamsList = []; array.forEach(layers, lang.hitch(this, function () { var queryParams = new Query(); queryParams.geometry = geom; queryParams.outFields = ['*']; queryParams.returnGeometry = false; queryParams.outSpatialReference = this.spatRef; queryParamsList.push(queryParams); })); return queryParamsList; }, executeSelect: function (selectedParcels, esriMap) { var promises = []; array.map(selectedParcels, lang.hitch(this, function (parcel) { var params = this.createQueryParams(esriMap.addressServices, parcel.geometry); array.map(esriMap.addressServices, lang.hitch(this, function (layer, index) { promises.push(layer.queryFeatures(params[index])); })); })); var qPromises = new all(promises); qPromises.then(lang.hitch(this, function (r) { lang.hitch(this, this.addAddressesToList(r)); }), lang.hitch(this, function (err){ console.info(err); })); } }); return sdcAddressSelector; });
Fabulous! With a few minor changes, this works perfectly. I've copied my final code below, but the changes I had to make are at lines 16 and 43 in your code. Basically, the response in your code (line 16) is an array of response objects, so I have added a nested forEach to get to the underlying features, and the parcel is a graphic (line 43), so I have to get the parcel.feature, geometry. Thanks so much for your help!
define(["dojo/_base/declare", "dojo/promise/all", "dojo/_base/lang", "dojo/_base/array", "esri/tasks/query"], function (declare, all, lang, array, Query) { var sdcAddressSelector = declare(null, { constructor: function () { this.addresses = []; }, addAddressesToList: function (response) { var _this = this; array.forEach(response, function (r) { array.forEach(r.features, function (f) { _this.addresses.push(f); }); }); _this.callAPI(); }, callAPI: function () { //api calls here }, createQueryParams: function (layers, geom) { var queryParamsList = []; array.forEach(layers, lang.hitch(this, function () { var queryParams = new Query(); queryParams.geometry = geom; queryParams.outFields = ['*']; queryParams.returnGeometry = false; queryParams.outSpatialReference = esriMap.map.spatialReference; queryParamsList.push(queryParams); })); return queryParamsList; }, executeSelect: function (selectedParcels) { mapUI.showWorking(); var _this = this; var promises = []; array.map(selectedParcels, lang.hitch(this, function(parcel) { var params = this.createQueryParams(esriMap.addressServices, parcel.feature.geometry); array.map(esriMap.addressServices, lang.hitch(this, function (layer, index) { promises.push(layer.queryFeatures(params[index])); })); })); var qPromises = new all(promises); qPromises.then(lang.hitch(this, function (r) { lang.hitch(this, this.addAddressesToList(r)); }), lang.hitch(this, function (err) { console.info(err); })); } }); return sdcAddressSelector; });
This was terrific. I had a series of queries to make and was using a loop. I had a heckuva time trying to get to sense of all my callbacks completing at different times. Welcome to async hell.
On a side note, I played some - only half successfully - with async/await before finding dojo/promise/all - is it true that this is a bad approach with the JSAPI ? Came across some post warning against it.