【问题标题】:JavaScript Wait until all async calls finishJavaScript 等待所有异步调用完成
【发布时间】:2016-03-16 06:54:24
【问题描述】:

我需要一些帮助来处理 JavaScript 中的异步调用。我有一个 for 循环,每个循环调用一个异步 HttpRequest,并将其响应添加到一个数组中。我希望程序等到所有异步调用完成后再继续不使用 jQuery(仅用于 DOM 操作)。我已经搜索了很多解决方案,但如果不大量更改我的代码或依赖 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
})

已更新:使用 Promise 进行了编辑,但仍然无法正常工作,可能是我做错了什么。

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);
})

【问题讨论】:

  • 查看承诺,特别是Promise.all
  • @elclanrs 我用承诺编辑了代码,但它仍然无法正常工作。我还没习惯用promise,所以可能我做错了什么

标签: javascript asynchronous promise


【解决方案1】:

对于 Promise,您不应使用 callback 参数。而是从 Promise 中调用 resolve/reject 函数。

不要将回调传递给调用,而是在.then 处理程序中链接您想要对承诺的结果执行的操作。

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);
    });
})

【讨论】:

    【解决方案2】:

    由于您使用的是 jQuery,因此您可以使用 Deferred Object 来链接 Promise。

    收集所有的 Promise 并使用 $.when 和传播运算符来等待所有的 Promise 解决。在所有的ajax请求都解决后,你可以使用then来运行一个函数。

    ES5 示例

    $(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);
        });
    });
    &lt;script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"&gt;&lt;/script&gt;

    ES6 示例

    $(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);
        });
    });
    &lt;script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"&gt;&lt;/script&gt;

    【讨论】:

    • 你为什么要删除需要 ES6 的通知?
    • 已添加,我将添加一个 ES5 兼容示例。我认为他是通过channels.map((x) =&gt; 代码使用 ES6
    【解决方案3】:

    难道不能只通过将 ajax 请求的计数作为变量来完成吗:

    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);
    })
    

    【讨论】:

    • 那个data_count应该是该循环函数的本地,而不是放在httpGet中。
    • AFAIK,httpGet 是一个自定义函数,因此可以始终将一个附加参数(在完成时触发的函数)传递给此函数,在某些情况下可以为 null。和 data_count 也可以作为比较的附加参数传递。
    • 是的,您当然可以修改自定义函数,但这并不意味着您应该。您正在破坏可重用性和封装性。
    猜你喜欢
    • 2011-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-22
    • 1970-01-01
    • 1970-01-01
    • 2016-07-07
    • 2019-03-15
    相关资源
    最近更新 更多