How to run the code sequentially using Deferred?

2019-07-16 03:38发布

Inside of this for-loop, I hope to force it to run the AJAX code block first. With the result (i.e. data.LocationId), I want to save that on the server, and then run the loop with i decreasing.

If you see my console.log, I hope it could be:

asyncProcess

data.LocationId 7615

asyncProcess

data.LocationId 7614

asyncProcess

data.LocationId 7613

But actually it is:

asyncProcess

asyncProcess

asyncProcess

data.LocationId 7615

data.LocationId 7614

data.LocationId 7613

How can this be achieved?

This is my code:

for (i = projectAreaSet.length-1; i >= 0; i--)
{
    function asyncProcess(geometry)
    {
        console.log("asyncProcess");
        var deferred = new Deferred(); //Dojo, not jQuery

        var locationProfile = { ProjectId: projectId }
        $.ajax({
            type: "POST",
            data: JSON.stringify(locationProfile),
            url: "api/LocationProfile/Create",
            contentType: "application/json",
        })
        .success(function (data)
        {
            LocationIdSet.push(data.LocationId);
            console.log("data.LocationId ", data.LocationId);
            var currentProjectGraphic = new esri.Graphic(geometry, newSymbol, attributes =
                { "ID": data.LocationId, "Type": 1}, null);
            var currentLayer = that.map.getLayer("Project");
            currentLayer.applyEdits([currentProjectGraphic], null, null);
            deferred.resolve();
        });
        return deferred.promise;
    }
    var saveProject = asyncProcess(projectAreaSet[i]);
}

2条回答
ら.Afraid
2楼-- · 2019-07-16 04:05

Since it appears that your ajax calls are all independent of one another (one doesn't depend on the other), you can run them all in parallel and use promises to keep the result in order so you can then process the result in order. This will generally be a faster end-to-end execution time, but will still let you process the results in order. You can do that using jQuery promises like this:

var promises = [];
for (var i = projectAreaSet.length - 1; i >= 0; i--) {
    (function(geometry) {
        promises.push($.ajax({
            type: "POST",
            data: JSON.stringify({ProjectId: projectId}),
            url: "api/LocationProfile/Create",
            contentType: "application/json"
        }).then(function(data) {
            // make resolved value be data and geometry together so we can
            // process them in order together later
            return {geometry: geometry, data: data};
        }));
    })(projectAreaSet[i]);
}
$.when.apply($, promises).then(function() {
    var results = Array.prototype.slice.call(arguments);
    // process results array here in the order they were requested
    results.forEach(function(obj) {
        var data = obj.data;
        var geometry = obj.geometry;
        LocationIdSet.push(data.LocationId);
        console.log("data.LocationId ", data.LocationId);
        var currentProjectGraphic = new esri.Graphic(geometry, newSymbol, attributes = {
            "ID": data.LocationId,
            "Type": 1
        }, null);
        var currentLayer = that.map.getLayer("Project");
        currentLayer.applyEdits([currentProjectGraphic], null, null);
    });
    // all results processing done here - can run any final code here
});
查看更多
仙女界的扛把子
3楼-- · 2019-07-16 04:14

While I support jfriend00's suggestion of running the requests in parallel, you specifically asked:

How can this be achieved?

If you really want them run serially/sequentially, one technique would be to run the next iteration from the success() callback.

In the code below, the for statement has been removed, and i has been made a parameter of the function asyncProcess(). Then in the success callback, if the value of i is greater than 0, it calls the function again after subtracting one value from i (just like the for loop did).

 var i = projectAreaSet.length - 1;
 asyncProcess(projectAreaSet[i], i);
 function asyncProcess(geometry, i) {
      $.ajax({
          type: "POST",
          //...other options
       })
      .success(function(data) {
           LocationIdSet.push(data.LocationId);
           //instantiate new esri.Graphic, call currentLayer.applyEdits()
           //then run the next iteration, if appropriate
           if (i > 0) {
               asyncProcess(projectAreaSet[i-1], i-1);
           }
           deferred.resolve();
       });

See this demonstrated in this plunker.

Update:

After reading the discusssion of the answer, it appears you will aim for a parallel approach. For that, since jQuery is being utilized, look at using the .when() function- passing an array of promises (e.g. Returned by $.ajax()).

Take a look at this updated plunker. You will notice that the function asyncProcess has been updated to return the call to $.ajax(), which is a jqXHR object, which "implements the Promise interface"1.

Using that change, the promises can be added to an array. Then use the spread operator (i.e. ...) to pass the promises to $.when.

$.when(...promises).done(function() { ... });

The spread operator is added in ES-6 so older browser like IE won't support it. If support is needed for such browsers, apply can be used to call $.when with those promises.

$.when.apply(null, promises).done(function() { ... });

In the .done() callback of the call to $.when(), each argument is an array, where the first element is the data. Putting it all together, we have code like below:

var promises = [];
for (i = projectAreaSet.length - 1; i >= 0; i--) {
    promises.push(asyncProcess(projectAreaSet[i], i));
}
$.when(...promises).done(function() {
    Array.prototype.forEach.call(arguments, function(response) {
      var data = response[0];
      console.log("data.LocationId ", data.LocationId);
      LocationIdSet.push(data.LocationId);
      geometry = projectAreaSet[data.i];
      /* continue with normal code:
      var currentProjectGraphic = new esri.Graphic(geometry, newSymbol, attributes =
            { "ID": data.LocationId, "Type": 1}, null);
      var currentLayer = that.map.getLayer("Project");
        currentLayer.applyEdits([currentProjectGraphic], null, null);*/
    });
});

Well, actually you asked "How to make this happen" in the original post but somebody edited your post... 1https://api.jquery.com/jquery.post/#jqxhr-object

查看更多
登录 后发表回答