Necesito ayuda con el manejo de llamadas asíncronas en JavaScript. Tengo un bucle for, cada bucle llama a una HttpRequest asíncrona y agrega su respuesta a una matriz. Quiero que el programa espere hasta que finalicen todas las llamadas asíncronas antes de continuar sin jQuery (que solo se usa para la manipulación DOM). He buscado bastante soluciones, pero ninguna realmente funcionó sin cambiar mucho mi código o confiar en 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
})

ACTUALIZADO : editado con Promise, pero aún no funciona, tal vez hice algo mal.

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);
})
1
Ryan Aleksander 10 dic. 2015 a las 18:06

3 respuestas

La mejor respuesta

Con las promesas, no debe usar un parámetro callback. Llame a las funciones resolve / reject de la promesa en su lugar.

En lugar de pasar una devolución de llamada a la llamada, encadene las cosas que desea hacer con el resultado de la promesa en un controlador .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);
    });
})
1
Bergi 10 dic. 2015 a las 15:25

¿No puede hacerse simplemente manteniendo el recuento de solicitudes ajax como una 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);
})
0
Raman 10 dic. 2015 a las 15:30

Como está utilizando jQuery, puede usar el Objeto diferido para encadenar las promesas.

Recoja todas las promesas y use $.when con el operador de propagación para esperar a que se resuelvan todas las promesas. Puede usar then para ejecutar una función después de que se resuelvan todas las solicitudes de ajax.

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

Ejemplo de 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);
    });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
0
Rovak 10 dic. 2015 a las 15:39