JavaScript Wait until all async calls finish

2020-05-03 16:47发布

I need some help with handling async calls in JavaScript. I have a for loop, each loop calls an async HttpRequest, and adds its response to an array. I want the program to wait until all the async calls are finished before proceeding without jQuery (which is only used for DOM manipulation). I've searched quite bit for solutions but none really worked without heavily changing my code or relying on jQuery.

function httpGet(theUrl, callback) {
    var xmlRequest = new XMLHttpRequest();
    xmlRequest.onreadystatechange = function() {
        if (xmlRequest.readyState == 4 && xmlRequest.status == 200) {
            callback(xmlRequest.responseText);
        }
    }
    xmlRequest.open("GET", theUrl, true);
    xmlRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    xmlRequest.setRequestHeader("Accept", "application/json");
    xmlRequest.send(null);
}
$(document).ready(function() {    
    var channels = ["freecodecamp", "storbeck", "terakilobyte", "habathcx","RobotCaleb","thomasballinger","noobs2ninjas","beohoff"];
    var urls = channels.map((x) => "https://api.twitch.tv/kraken/channels/" + x);
    var data = [];
    (function(urls, data) {
        urls.forEach(function(url) {  
            function(resolve, reject) {
                httpGet(url, function(response) {
                    data.push(JSON.parse(response));
                })
            };
        })
    })(urls, data);

    // Continue after all async calls are finished
})

UPDATED: Edited with Promise, but still not working, maybe I did something wrong.

function httpGet(theUrl, callback) {
    return new Promise(function(resolve, reject) {
        var xmlRequest = new XMLHttpRequest();
        xmlRequest.onreadystatechange = function() {
            if (xmlRequest.readyState == 4 && xmlRequest.status == 200) {
                callback(xmlRequest.responseText);
            }
        }
        xmlRequest.open("GET", theUrl, true);
        xmlRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
        xmlRequest.setRequestHeader("Accept", "application/json");
        xmlRequest.send(null);
    })
}
$(document).ready(function() {    
    var channels = ["freecodecamp", "storbeck", "terakilobyte", "habathcx","RobotCaleb","thomasballinger","noobs2ninjas","beohoff"];
    var urls = channels.map((x) => "https://api.twitch.tv/kraken/channels/" + x);
    var data = [];
    var promises = [];
    (function(urls, data) {
        urls.forEach(function(url) {  
            var promise = httpGet(url, function(response) {
                data.push(JSON.parse(response));
            });
            promises.push(promise);
        })

        Promise.all(promises).then(function() {
            console.log(data);
        })
    })(urls, data);
})

3条回答
姐就是有狂的资本
2楼-- · 2020-05-03 17:04

With promises, you should not use a callback parameter. Call the resolve/reject functions from the promise instead.

Instead of passing a callback to the call, chain the things you want to do with the result of the promise in a .then handler.

function httpGet(theUrl) {
    return new Promise(function(resolve, reject) {
        var xmlRequest = new XMLHttpRequest();
        xmlRequest.onreadystatechange = function() {
            if (xmlRequest.readyState == 4) {
                if (xmlRequest.status == 200) 
                    resolve(xmlRequest.responseText);
    //              ^^^^^^^
                else
                    reject(new Error(xmlRequest.statusText)); // or something
            }
        }
        xmlRequest.open("GET", theUrl, true);
        xmlRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
        xmlRequest.setRequestHeader("Accept", "application/json");
        xmlRequest.send(null);
    });
}
$(document).ready(function() {    
    var channels = ["freecodecamp", "storbeck", "terakilobyte", "habathcx","RobotCaleb","thomasballinger","noobs2ninjas","beohoff"];
    var urls = channels.map((x) => "https://api.twitch.tv/kraken/channels/" + x);
    var promises = urls.map(function(url) {
//                      ^^^ simpler than forEach+push
        var promise = httpGet(url); // <-- no callback
        return promise.then(JSON.parse);
    });

    Promise.all(promises).then(function(data) {
//                                      ^^^^
        console.log(data);
    });
})
查看更多
一夜七次
3楼-- · 2020-05-03 17:13

Can't it be done by just keeping the count of ajax requests as a variable:

var urls_count, data_count = 0;
function httpGet(theUrl, callback, onComplete) {
    var xmlRequest = new XMLHttpRequest();
    xmlRequest.onreadystatechange = function() {
        if (xmlRequest.readyState == 4 && xmlRequest.status == 200) {
            callback(xmlRequest.responseText);
        }
        if(xmlRequest.readyState == 4){
            data_count += 1
            if(urls_count == data_count){
                //this is called when all ajax calls complete
                onComplete();
            }
        }
    }
    xmlRequest.open("GET", theUrl, true);
    xmlRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    xmlRequest.setRequestHeader("Accept", "application/json");
    xmlRequest.send(null);
}
$(document).ready(function() {    
    var channels = ["freecodecamp", "storbeck", "terakilobyte", "habathcx","RobotCaleb","thomasballinger","noobs2ninjas","beohoff"];
    var urls = channels.map((x) => "https://api.twitch.tv/kraken/channels/" + x);
    var data = [];
    urls_count = urls.length;
    var onComplete = function(){
        //your code after all ajax completes.
    }
    (function(urls, data) {
        urls.forEach(function(url) {  
            function(resolve, reject) {
                httpGet(url, function(response) {
                    data.push(JSON.parse(response));
                }, onComplete)
            };
        })
    })(urls, data);
})
查看更多
贪生不怕死
4楼-- · 2020-05-03 17:25

Since you are using jQuery you can use the Deferred Object to chain promises.

Collect all the promises and use $.when with spread operator to wait for all promises to resolve. You can use then to run a function after all ajax requests are resolved.

ES5 Example

$(document).ready(function () {

    var channels = ["freecodecamp", "storbeck", "terakilobyte", "habathcx", "RobotCaleb", "thomasballinger", "noobs2ninjas", "beohoff"];
    var urls = channels.map(function (x) {
        return "https://api.twitch.tv/kraken/channels/" + x;
    });
    var data = [];
    var promises = urls.map(function (url) {
        return $.get(url).then(function (response) {
            data.push(response);
        });
    });

    $.when.apply($, promises).then(function () {
        console.log('done', data);
    });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

ES6 Example

$(document).ready(function() {    
    var channels = ["freecodecamp", "storbeck", "terakilobyte", "habathcx","RobotCaleb","thomasballinger","noobs2ninjas","beohoff"];
    var urls = channels.map((x) => "https://api.twitch.tv/kraken/channels/" + x);
    var data = [];
    var promises = urls.map((url) => $.get(url).then((response) => {
        data.push(response);
    }));

    $.when(...promises).then(function() {
        console.log('done', data);
    });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

查看更多
登录 后发表回答