How to nest dojo async calls and wait until all are complete

6486
9
Jump to solution
05-19-2015 09:24 AM
ShawnHolyoak
Occasional Contributor

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;
});
Tags (3)
0 Kudos
1 Solution

Accepted Solutions
RobertScheitlin__GISP
MVP Emeritus

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;
  });

View solution in original post

9 Replies
thejuskambi
Occasional Contributor III

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;
});
ShawnHolyoak
Occasional Contributor

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.

0 Kudos
thejuskambi
Occasional Contributor III

Did you make the change on line 31 as well?

0 Kudos
ShawnHolyoak
Occasional Contributor

I did, thanks.

0 Kudos
ChrisSmith7
Frequent Contributor

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

0 Kudos
ShawnHolyoak
Occasional Contributor

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.

0 Kudos
RobertScheitlin__GISP
MVP Emeritus

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;
  });
ShawnHolyoak
Occasional Contributor

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;
});
Arne_Gelfert
Occasional Contributor III

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. 

0 Kudos