简体   繁体   中英

How would I defer this javascript call?

I'm trying to figure out how to accomplish this workflow, but can't seem to nail it. I've got n number of <select> elements on a page. When the page loads, for each <select> element, I need to make a $.get(...); call. Once all of those calls are done, then, and only then do I need to run an additional function. Here is some example code to better explain:

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

Using the code above, doMoreWork() is usually called before all of the async $.get(...); calls have had a chance to return; which is not what I want. I need to have all of the $.get(...); calls complete before doMoreWork() can be called. Basically I need a callback of sorts to execute once ALL of the $.get(...); calls in the above example have finished.

How would I go about accomplishing this?

  1. Every time you call doWork , increment a counter.

  2. Every time a response comes back, decrement the counter.

  3. Have the callback invoke doMoreWork when the counter reaches 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);
    });
});

I would write a class something like:

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

Keep track of how many Ajax calls have yet to complete, and execute doMoreWork() when there are none left.

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

You may also want to catch ajax errors.

You can use jQuery's $.when to join together multiple Deferred objects to one:

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

Basically, $.when takes multiple Deferred objects as each argument and wraps them together as one Deferred by keeping track of the number of completed sub-deferres, similar to how a couple of the answers here implemented it manually.

A more readable version of the above code is:

var requests = [];
$('select').each(function(index, selectEl) {
    request.push($.ajax(....));
}
$.when.apply($, requests).done(function() {
    // All AJAX calls finished
});

Maybe you could use the JavaScript underscore library's after function .

(note: I haven't tested this code)

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

Use Deferred:

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

Since it looks like you're doing jQuery, you could use the $.ajaxStop event handler... http://api.jquery.com/ajaxStop/

EDIT Said $.ajaxComplete instead of the correct $.ajaxStop... Fixed now...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM