Ray
Ray

Reputation: 4929

How do I wait for a .each loop that makes async calls?

I have some javascript code that updates some data to a database using a http handler, but this async call is made inside an .each loop. At the end of the loop I make a call to function CancelChanges() that refreshed the page. The problem is that the page seems to refresh before the database is updated. The .each loop seems to finish after the call to CancelChanges(). How can I make sure the page is refreshed after all the async calls are completed in the .each loop?

function SaveChanges() {
    if (PreSaveValidation()) {
        var allChangesSucceeded = true;
        var studioId = $("#param_studio_id").val();
        var baseDate = $("#param_selected_month").val().substring(6, 10) + $("#param_selected_month").val().substring(0,2);
        var currency = "CAD";
        var vacationPct = null;
        var gvAdmissible = null;

        $(".editable-unsaved").each( function() {
            var newSalary = $(this).text();
            var disciplineId = $(this).data("disciplineid");
            var seniorityId = $(this).data("seniorityid");
            var handlerCommand = "";
            if ($(this).data("valuetype") === "inflated") {
                handlerCommand = "AddAverageSalary";
            } else if ($(this).data("valuetype") === "actual") {
                handlerCommand = "UpdateAverageSalary";
            }
            $.get("WS/AverageSalary.ashx", { command: handlerCommand, studio_id: studioId, discipline_id: disciplineId, seniority_id: seniorityId, base_date: baseDate, currency: currency, salary: newSalary, vacation_pct: vacationPct, gv_admissible: gvAdmissible }).done(function (data) {
                if (data != "1") {
                    $(this).removeClass("editable-unsaved");
                    allChangesSucceeded = true;
                }
                else {
                    alert('fail');
                    allChangesSucceeded = false;
                }
            });
        });
        if(allChangesSucceeded) CancelChanges();
    }
}

function CancelChanges() {
    var href = window.location.href;
    href = href.split('#')[0];
    window.location.href = href;
}

Upvotes: 4

Views: 2698

Answers (5)

James G.
James G.

Reputation: 2904

I think you need to change your structure a little bit, using a counter and calling CancelChanges when the counter equals the number of calls.

function SaveChanges() {
    if (PreSaveValidation()) {
        var studioId = $("#param_studio_id").val();
        var baseDate = $("#param_selected_month").val().substring(6, 10) + $("#param_selected_month").val().substring(0,2);
        var currency = "CAD";
        var vacationPct = null;
        var gvAdmissible = null;

        var editableUnsaveds = $(".editable-unsaved"); //cache the selector here, because selectors are costly
        var numOfGetsReturned = 0;            

        editableUnsaveds.each( function() {
            var newSalary = $(this).text();
            var disciplineId = $(this).data("disciplineid");
            var seniorityId = $(this).data("seniorityid");
            var handlerCommand = "";
            if ($(this).data("valuetype") === "inflated") {
                handlerCommand = "AddAverageSalary";
            } else if ($(this).data("valuetype") === "actual") {
                handlerCommand = "UpdateAverageSalary";
            }
            $.get("WS/AverageSalary.ashx", { command: handlerCommand, studio_id: studioId, discipline_id: disciplineId, seniority_id: seniorityId, base_date: baseDate, currency: currency, salary: newSalary, vacation_pct: vacationPct, gv_admissible: gvAdmissible }).done(function (data) {
                if (data != "1") {
                    $(this).removeClass("editable-unsaved");
                }
                else {
                    alert('fail');
                }
                if(editableUnsaveds.length === ++numOfGetsReturned){
                   CancelChanges(); //now it should call when the final get call finishes.
                }
            });
        });
    }
}

function CancelChanges() {
    var href = window.location.href;
    href = href.split('#')[0];
    window.location.href = href;
}

Upvotes: 2

Bryce Johnson
Bryce Johnson

Reputation: 6901

I'd use promises. The q library is my favorite way to implement them. But since you're using JQuery, I'd recommend following a similar approach to what I outline below, but using $.when, instead of q.allSettled

I often use promises when scraping tons of websites at once -- I need to iterate through a long list of websites, make requests for content, and do something with the content when the requests return. The last thing I want to do is send requests one at a time, handling each one as it returns.

In the abstract, that looks something like this:

function scrapeFromMany() {
  var promises = [];

    _.forEach(urls, function(url) {

      // this makes the request
      var promise = scraper(url);

      // this stores the promise with the others you iterate through
      promises.push(promise); 
    });

  q.allSettled(promises).then(function(res) {
    // this function is executed when all of the promises (requests) have been resolved 

   console.log("Everything is done -- do something with the results.", res);
  });
}

Fwiw, promises aren't that easy to grok if you've never used them. If that's the case, plan on spending some time getting up to speed with the concepts. They'll change (for the much much better) the way you write async javascript, and they really are the blessed path with these sorts of operations.

Upvotes: 1

gufoe
gufoe

Reputation: 1

This happens because you are not waiting for the requests to complete to proceed with the loop. To achieve so you have to set the "async" flag to false. The call to the server should be like this:


    $.ajax({
        url: "WS/AverageSalary.ashx",
        async: false,
        data:{ command: handlerCommand, studio_id: studioId, discipline_id: disciplineId, seniority_id: seniorityId, base_date: baseDate, currency: currency, salary: newSalary, vacation_pct: vacationPct, gv_admissible: gvAdmissible },
        success: function (data) {
            if (data != "1") {
                $(this).removeClass("editable-unsaved");
                allChangesSucceeded = true;
            }
            else {
                alert('fail');
                allChangesSucceeded = false;
            }
        }
    });

Upvotes: 0

lemieuxster
lemieuxster

Reputation: 1081

You could try using Promises and jQuery $.when

Store a list of the ajax call promises:

var defereds = [];

$(".editable-unsaved").each( function() {
    //...
    defereds.push($.get("WS/AverageSalary.ashx" /*...*/));
}
$.when.apply($, defereds).done(function() {
    CancelChanges();
});

This should, hopefully, wait for all the ajax calls to finish before calling CancelChanges()

Upvotes: 6

nbrooks
nbrooks

Reputation: 18233

Asynchronously call your check function within the "done" function handler. Keep track of how many requests have completed, and only do your processing once that's equal to the total number of expected requests.

if (PreSaveValidation()) {
    var allChangesSucceeded = true;
    var length = $(".editable-unsaved").length;
    var completedCount = 0;
    // ...

    $(".editable-unsaved").each( function() {
        // ...
        $.get("WS/AverageSalary.ashx", data).done(function (data) {

            completedCount++;

            if (data != "1") {
                $(this).removeClass("editable-unsaved");
                // don't set all changes succeeded to true here
            }
            else {
                alert('fail');
                allChangesSucceeded = false;
            }

            isComplete(length, completedCount, allChangesSucceeded);
        });
    });
}

function isComplete(totalLength, currentLength, allChangesSucceeded) {
    if (currentLength == totalLength) {

        // should this be !allChangesSucceeded?
        if (allChangesSucceeded) CancelChanges();
    }
}

Upvotes: 0

Related Questions