Estoy tratando de descubrir cómo lograr este flujo de trabajo, pero parece que no puedo concretarlo. Tengo n número de <select> elementos en una página. Cuando se carga la página, para cada elemento <select>, necesito hacer una llamada $.get(...);. Una vez que todas esas llamadas están hechas, entonces, y solo entonces, necesito ejecutar una función adicional. Aquí hay un código de ejemplo para explicar mejor:

function doWork(selectEl) {
    var getData = ...; // build request data based on selectEl

    $.get('/foo/bar', getData, function (data) {
        // Do something to selectEl with the result
    });
}

function doMoreWork() {
    // Do something with all the selects now that they are ready
}

$(function () {
    // For each of the select elements on the page
    $('select').each(function(index, selectEl) {
        // Go do some AJAX-fetching of additional data
        doWork(selectEl);
    });

    // Once *all* the $.get(...) calls are done, do more things
    doMoreWork();
});

Usando el código anterior, doMoreWork() generalmente se llama antes de que todas las llamadas asíncronas $.get(...); hayan tenido la oportunidad de regresar; que no es lo que quiero Necesito tener todas las $.get(...); llamadas completas antes de que se pueda llamar a doMoreWork(). Básicamente, necesito una devolución de llamada para ejecutar una vez que TODAS las llamadas $.get(...); en el ejemplo anterior hayan terminado.

¿Cómo haría para lograr esto?

5
ckittel 24 ago. 2011 a las 23:19

7 respuestas

La mejor respuesta

Mantenga un registro de cuántas llamadas Ajax aún no se han completado y ejecute doMoreWork() cuando no quede ninguna.

$(function(){
    var workLeft = $('select').length;

    function doWork(selectEl) {
        var getData = ...; // build request data based on selectEl

        $.get('/foo/bar', getData, function (data) {
            // Do something to selectEl with the result

            // If done all work
            if(!(--workLeft)){
                doMoreWork();
            }                
        });
    }

    function doMoreWork() {
        // Do something with all the selects now that they are ready
    }

    // For each of the select elements on the page
    $('select').each(function(index, selectEl) {
        // Go do some AJAX-fetching of additional data
        doWork(selectEl);
    });
});

También es posible que desee detectar errores de ajax.

3
Paul 24 ago. 2011 a las 19:58

Como parece que estás haciendo jQuery, puedes usar el controlador de eventos $ .ajaxStop ... http://api.jquery.com/ajaxStop/

EDITAR dijo $ .ajaxComplete en lugar del correcto $ .ajaxStop ... Solucionado ahora ...

-1
gislikonrad 24 ago. 2011 a las 19:34

Escribiría una clase algo como:

function synchronizer(query, action, cleanup) {
    this.query = query;
    this.action = action;
    this.cleanup = cleanup;
    this.remaining = query.length;
    this.complete = function() {
        this.remaining -= 1;
        if (this.remaining == 0) { this.cleanup(query); }
    }
    this.run = function() {
        query.each(function(index, which) { action(which, this.complete); })
    }
}

// Aargh. Expecting doWork() to call a passed-in continuation seems ugly to me
// as opposed to somehow wrapping doWork within the synchronizer... but I can't
// think of a way to make that work.

function doWork(element, next) {
    var getData = ...; // build request data based on element

    $.get('/foo/bar', getData, function(data) {
        // Do something to element with the result, and then
        next();
    });
}

function doMoreWork(elements) {
    // Do something with all the selects now that they are ready
}

new synchronizer($('select'), doWork, doMoreWork).run();
4
Karl Knechtel 24 ago. 2011 a las 21:07

Puede usar jQuery's $.when para unir varios objetos diferidos en uno:

$.when.apply($, $('select').map(function(index, selectEl) {
    return $.ajax(....);
}).get()).done(function() {
    // All AJAX calls finished
});

Básicamente, $.when toma múltiples objetos diferidos como cada argumento y los agrupa como uno diferido al realizar un seguimiento del número de sub-aplazamientos completados, de forma similar a cómo un par de respuestas aquí lo implementaron manualmente.

Una versión más legible del código anterior es:

var requests = [];
$('select').each(function(index, selectEl) {
    request.push($.ajax(....));
}
$.when.apply($, requests).done(function() {
    // All AJAX calls finished
});
2
shesek 24 ago. 2011 a las 20:40

Tal vez podría utilizar la función de subrayado de la biblioteca de subrayado de JavaScript.

(nota: no he probado este código)

var numberOfSelectElements = n;
var finished = _after(numberOfSelectElements, doMoreWork);

function doWork(selectEl) {
    var getData = ...; // build request data based on selectEl

    $.get('/foo/bar', getData, function (data) {
        finished();
    });
}

function doMoreWork() {
    // Do something with all the selects now that they are ready
}

$(function () {
    // For each of the select elements on the page
    $('select').each(function(index, selectEl) {
        // Go do some AJAX-fetching of additional data
        doWork(selectEl);
    });
});
2
Richard JP Le Guen 24 ago. 2011 a las 21:25
  1. Cada vez que llame a doWork, incremente un contador.

  2. Cada vez que vuelva una respuesta, disminuya el contador.

  3. Haga que la devolución de llamada invoque doMoreWork cuando el contador llegue a 0.

var counter = 0;

function doWork(selectEl) {
    counter++;
    var getData = ...; // build request data based on selectEl

    $.get('/foo/bar', getData, function (data) {
        counter--;
        if( !counter ) { doMoreWork(); }
    });
}

function doMoreWork() {
    // Do something with all the selects now that they are ready
}

$(function () {
    // For each of the select elements on the page
    $('select').each(function(index, selectEl) {
        // Go do some AJAX-fetching of additional data
        doWork(selectEl);
    });
});
5
user113716 24 ago. 2011 a las 19:24

Uso diferido:

function doWork(selectEl) {
    var getData = ...;

    // return Deferred object
    return $.get('/foo/bar', getData, function (data) {

    });
}


var selects = $('select');

function doItem(i) {
    if(selects.length === i) return doMoreWork(); // if no selects left, abort and do more work
    $.when(doWork(selects.get(i)).then(function() { // fetch and do next if completed
        doItem(i + 1);
    });
});

doItem(0); // start process
1
pimvdb 24 ago. 2011 a las 19:32