Ajax call in for loop wait until ALL done (error o

2019-08-11 18:19发布

问题:

I am making AJAX calls in a for loop and I would like to wait until all of them are done before doing something.

I am trying to use promises but there is something that I'm missing because it won't work. If an error is fired, my when call is fired immediately and I don't want to. I want to wait until ALL calls are done, no matter if it's success or error.

When all calls are done, I want the number of entries that succeeded and an array of errors.

Here is my code :

for (var i = 0; i < resp.propertiesList.length; i++) {
    var documentToSend = { 
        indexName: indexName, 
        type: type, 
        jsonContent: blabla 
    };

    var promise = _TOOLS.Ajax({
        type: 'POST',
        url: urlTest,
        data: documentToSend,
        contentType: 'application/x-www-form-urlencoded',
        dataType: 'json',
        success: function (dataResponse) {
            numberOfEntriesSuccess++;
        },
        error: function (xhr, ajaxOptions, errorThrown) {
            var errorResponse = JSON.parse(xhr.responseText);
            errorsList.push(errorResponse.ExceptionMessage + ". Skipping line.");
        }
    });

    promises.push(promise);
}

//When all ajax calls are finished, load report page and display informations
$.when.apply($, promises).done(function() { 
    console.log("DONE " + promises.length); 
    console.log(errorsList); 
})

I tried with .done, .then, .always, but none of them fire when I wish to. I read a lot about promises on the internet but I'm definitely missing something and it's driving me crazy.

Thanks for your help !

回答1:

The way I find easiest to handle these things is keeping track of the status of each of the Async calls in an object or array. Each time a call completes, flag it as done and check if there are any more running. You could even just add a boolean to your object to check if asyncIsBusy, or a property for ajaxStatus.

I am assuming here that indexName and typeare coming from resp.propertiesList(you don't seem to use your ianywhere, so I guess you left that out by accident?).

for (var i = 0; i < resp.propertiesList.length; i++) {
    var documentToSend = { 
        indexName: indexName, 
        type: type, 
        jsonContent: blabla 
    };

    sendDocumentAjax(resp.propertiesList[i], documentToSend)
}

function sendDocumentAjax(listObj, documentData){
    listObj.ajaxStatus = 'pending';
    _TOOLS.Ajax({
        type: 'POST',
        url: urlTest,
        data: documentData,
        contentType: 'application/x-www-form-urlencoded',
        dataType: 'json',
        success: function (dataResponse) {
            listObj.ajaxStatus = 'success';
        },
        error: function (xhr, ajaxOptions, errorThrown) {
            var errorResponse = JSON.parse(xhr.responseText);
            listObj.ajaxStatus = 'error: '+ errorResponse;
        }
        always: checkAjaxStatuses;
    });
}

function checkAjaxStatuses(){
    var pending = [];
    var successes = [];
    var errors = [];
    for (var i = 0; i < resp.propertiesList.length; i++) {
        if(resp.propertiesList[i].ajaxStatus === 'pending'){
            pending.push(resp.propertiesList[i]);
        }
        if(resp.propertiesList[i].ajaxStatus === 'success'){
            successes.push(resp.propertiesList[i]);
        }
        if(resp.propertiesList[i].ajaxStatus.indexOf('error') !== -1){
            errors.push(resp.propertiesList[i]);
        }
    }

    console.log('ajax completed.');
    console.log(pending.length + ' pending.');
    console.log(successes.length + ' succeeded.');
    console.log(errors.length + ' failed.');
}

Note how I used a separate function to send the ajax, so a new closure is created for each object that you sent an ajax call for. You can't do all of this through anonymous functions because i would always be the maximum value in the callbacks (since the loop finishes before any ajax call is completed), leaving you no way to reference the original object you sent the Ajax for. Using a separate function circumvents this issue.



回答2:

Q.allSettled can help you on this, you may find the npm package here - https://www.npmjs.com/package/q.



回答3:

try setting async: false in ajax call

var promise = _TOOLS.Ajax({
                    type: 'POST',
                    url: urlTest,
                    data: documentToSend,
                    async: false,
                    contentType: 'application/x-www-form-urlencoded',
                    dataType: 'json',
                    success: function (dataResponse) {
                              numberOfEntriesSuccess++;
                    },
                    error: function (xhr, ajaxOptions, errorThrown) {
                               var errorResponse = JSON.parse(xhr.responseText);
                                errorsList.push(errorResponse.ExceptionMessage + ". Skipping line.");
                    }
                });


回答4:

Go for a recursive approach:

ajaxLoopArray(myArray, function(){ /* do something when finish */ });

function ajaxLoopArray(someArray, callbackFunction){
    if(someArray.length>0){ //condition to break the loop
        data = someArrapy.pop(); // pop removes the last element from an array and returns that element.
        $.ajax({
            data
        }).done(function(x){
            //success
        })
        .fail(function(){
            //catch the error here
        })
        .always(function(){
            ajaxLoopArray(someArray, callbackFunction); //recursive call: keep on looping independently of success or error
        });
    }else{
        callbackFunction(); //when everything was done
    }
}