user1309226
user1309226

Reputation: 759

jQuery deferred requests and promises: how to nest concurrent asyncronous calls

The situation I have is the following:

I need to save an array containing student data. Each student has two nested arrays of Scores Year 1 and Scores Year 2.

My goal is to save data for each student asynchrously - but before I can finalize this process and show the number of students added, I need to save data for Scores Year 1 and Scores Year 2 for each student. This data is also saved asyncronously.

The problem I have at the moment is that student records are saved while Scores Year 1 and Scores Year 2 are still being processed. The data for Scores Year 1 and Scores 2 must be saved asynchronously for there is no dependency between them. However by the time I'm ready to display the number of students added, all data should be saved. I must also say that the Scores Year 1 and Scores Year 2 data are depended on the new student id.

How can I achieve this using jQuery promises? If you run the jsFiddle below you'll see that the number of records entered appears BEFORE the Scores Year data is displayed whilst it should be the other way around. The display of the total number of students entered should be dependent on the completion of the Scores Years tasks. Also, I'm not interested in saving student records synchronously.

Here's the code: (Run code in jsFiddle here)

function performUpdate() {

    var arr_promises = [];

    var students = [
        {
            id: 1, name: 'John', scoresYear1: [{ subject: 'maths', score: 8 },
               { subject: 'chemistry', score: 7 }], scoresYear2: [{ subject: 'maths', score: 9 }, { subject: 'chemistry', score: 8 }]
        },
        {
            id: 2, name: 'Mary', scoresYear1: [{ subject: 'maths', score: 9 },
               { subject: 'chemistry', score: 8 }], scoresYear2: [{ subject: 'maths', score: 10 }, { subject: 'chemistry', score: 7 }]
        }
    ];

    $.each(students, function (index, student) {

        arr_promises.push(SaveStudent(student));

    });

    $.when.apply($, arr_promises).done(function () {
        var msg = '<p>Total number of students added: ' + arguments.length + '</p>';

        $("div").append(msg);
    });
};

function SaveStudent(student) {

    var dfrd = $.Deferred();

    executeQueryAsync(student,

     function () {

         var arrScoreYears = [];

         // Save scores year 1
         $.each(student.scoresYear1, function (index, score) {
             executeQueryAsync(score,
                 function () {
                     arrScoreYears.push({ status: true });
                 },
                 function (sender, args) {
                     alert('Request failed.');
                 });
         });


         // Save scores year 2
         $.each(student.scoresYear2, function (index, score) {
             executeQueryAsync(score,
                 function () {
                     arrScoreYears.push({ status: true });
                 },
                 function (sender, args) {
                     alert('Request failed.');
                 });
         });


         $.when.apply($, arrScoreYears).done(function () {
             //console.log('added score year 1 - Subject:' + score.subject + ' for student: ' + student.name);

             var msg = '<p>Added Score Years for student: ' + student.name + '</p>';

             $("div").append(msg);

         }).then(function () {
             dfrd.resolve({ status: true });
         });;


     },

    // fail
    function (sender, args) {
        alert('Request failed.');
    });
} // end of SaveStudent


function executeQueryAsync(student, callbackSuccess, callbackFail) {

    //simulate server call

    setTimeout(function () {
        callbackSuccess({ status: true });
    }, 3000)
}

Upvotes: 0

Views: 147

Answers (1)

76484
76484

Reputation: 8993

If you want to use $.when then you must pass it one or more deferred objects - at least if you want when to have any meaning. If you are not passing any deferred objects to $.when then it will execute immediately. Because SaveStudent does not return a value, you are feeding an empty array to $.when - so your "Total number of students added:..." message will get appended almost instantly.

(If you don't believe me, see this fiddle: http://jsfiddle.net/76484/rtdk8ono/)

So SaveStudent needs to return a deferred object:

function SaveStudent(student) {
    var dfrd = $.Deferred();
    /* other SaveStudent stuff */

    return dfrd;
}

Having done that, you found that this change made "no difference". This is correct - almost. This is because dfrd is getting resolved almost immediately for basically the same reason as explained above. dfrd gets resolved when all of the deferred objects in arrScoreYears are resolved, but there are no deferred objects in arrScoreYears - so dfrd is resolved immediately.

So let's add deferreds to arrScoreYears. In the interest of DRYness, I will create a single function to accomplish this so that we don't have rewrite the same code for saving year 1 scores and year 2 scores:

var saveYearScores = function (year_scores, deferreds) {
    $.each(year_scores, function (index, score) {
        var deferred = $.Deferred();
        deferreds.push(deferred);     
        executeQueryAsync(score,
            function () {
                deferred.resolve();
             },
             function (sender, args) {
                 deferred.fail();
                 alert('Request failed.');
             });
     });
};

Notice that we create a deferred for each year score we are asynchronously saving. I have chosen to pass the array that will contain the deferreds into the function so that it can append the new deferred objects to the array. What is important is that for each call to SaveStudent, we have an array of deferred objects, each representing an asynchronous save score call.

Now we can replace those each calls in SaveStudent with calls to our new function:

saveYearScores(student.scoresYear1, arrScoreYears);
saveYearScores(student.scoresYear2, arrScoreYears);

And now the $.when call will actually wait for our save score calls to finish!

I have updated your fiddle: http://jsfiddle.net/76484/w1pd0cns/4/

Upvotes: 1

Related Questions