3

I have a for loop in a search function, with a function that does a callback inside the loop, and I want to execute a BUILD() function after the loop, and after all the callbacks are completed. I am not sure how to do that, because the loop finishes before all the callbacks are done. The callbacks are API requests to get me data, and I want to BUILD() with that data.

I read up on deferred, so I tried to put the for loop inside a function to the deferred, and then calling BUILD() on '.then( ... )'. But that doesn't seem to work - I think I am understanding it wrong.

HELP?!

Note, this is using the Google Maps Places API (search and getDetails).

var types = {
    'gym' : 'fitness, gym',
    'grocery_or_supermarket': ''
}

function search() {
    for (var key in types) {
         var request = { ... };
         service.search(request, searchCallback);
    }
    // PROBLEM AREA
    BUILD();
}

function searchCallback(results, status) {
    for (var i = 0; i < results.length; i++) {
        var request = { ... };
        service.getDetails(request, detailsCallback);
    }
}

function detailsCallback(place, status) {
    // add place marker to maps and assign info window and info window event
}

2 Answers 2

3

With a small modification of your code, it can be achieved.

var total = 1337; // Some number
var internal_counter = 0;
var fn_callback = function() {
    searchCallback.apply(this, arguments);
    if (++internal_counter === total) {
        BUILD();
    }
};
for (var i=0; i<total; i++) {
    service.search(request, fn_callback);
    ...

Explanation

First, we create a local function and variable.

  • The variable is a counter, which is increased when the callback is called.
  • The function is passed to the asynchronous method (service.search), which calls the original callback. After increasing the counter, check the value of the counter against the variable which holds the total number of iterations. If these are equal, call the finishing function (BUILD).

A complex case: Dealing with nested callbacks.

var types = { '...' : ' ... ' };

function search() {
    var keys = Object.keys(types);
    var total = keys.length;
    // This counter keeps track of the number of completely finished callbacks
    //  (search_callback has run AND all of its details_callbacks has run)
    var internal_counter = 0;

    for (var i=0; i<total; i++) {
        var request = { '...' : ' ... ' };
        services.search(request, fn_searchCallback);
    }

    // LOCAL Function declaration (which references `internal_counter`)
    function fn_searchCallback(results, status) {
        // Create a local counter for the callbacks
        // I'm showing another way of using a counter: The opposite way
        // Instead of counting the # of finished callbacks, count the number
        //  of *pending* processes. When this counter reaches zero, we're done.
        var local_counter = results.length;
        for (var i=0; i<results.length; i++) {
            service.getDetails(request, fn_detailsCallback);
        }
        // Another LOCAL function (which references `local_counter`)
        function fn_detailsCallback(result, status) {

            // Run the function logic of detailsCallback (from the question)
            // " ... add place marker to maps and assign info window ... "

            // Reduce the counter of pending detailsCallback calls.
            //   If it's zero, all detailsCallbacks has run.
            if (--local_counter === 0) {
                // Increase the "completely finished" counter
                //  and check if we're finished.
                if (++internal_counter === total) {
                    BUILD();
                }
            }
        } // end of fn_detailsCallback
    } // end of fn_searchCallback
}

The function logic is explained in the comments. I prefixed the heading of this section with "Complex", because the function makes use of nested local functions and variables. A visual explanation:

var types, BUILD;
function search
    var keys, total, internal_counter, fn_searchCallback;
    function fn_searchCallback
        var result, status; // Declared in the formal arguments
        var local_counter, i, fn_detailsCallback;
        function fn_detailsCallback
            var result, status; // Declared in the formal arguments

In the previous picture, each indention level means a new scope Explanaation on MDN.
When a function is called, say, 42 times, then 42 new local scopes are created, which share the same parent scope. Within a scope, declared variables are not visible to the parent scope. Though variables in the parent scope can be read and updated by variables in the "child" scope, provided that you don't declare a variable with the same name. This feature is used in my answer's function.

8
  • What do you mean by searchCallback.apply(this, arguments); ? I don't understand this line - what should I put inside arguments? By default, the searchCallback takes in 'results' and 'status', but that wouldn't be available from within fn_callback.
    – gruuuvy
    Commented Jun 30, 2012 at 17:33
  • @poleapple .apply(this, arguments) is an universal way to invoke functions seamlessly. See the linked documentation for an explanation on the .apply method. If the context does not matter, you can also use fn_callback = function(a,b){searchCallback(a,b);} for example.
    – Rob W
    Commented Jun 30, 2012 at 17:40
  • Okay, thanks! This works great for my first searchCallback. However, the detailsCallback inside the searchCallback doesn't execute until way after the searchCallback is done, so I think I have to do a double counter or something...
    – gruuuvy
    Commented Jun 30, 2012 at 17:57
  • @poleapple I don't see a relationship between that comment, and your current code. In your current code, detailsCallback is immediately executed for each response.
    – Rob W
    Commented Jun 30, 2012 at 18:03
  • So I did exactly as you showed, BUILD is called after all the searchCallbacks are done, however, the detailsCallback only start executing AFTER the searchCallbacks are done as well. So BUILD right now executes before the detailsCallback are done executing, but I need to execute BUILD after the detailsCallback are done.
    – gruuuvy
    Commented Jun 30, 2012 at 18:07
0

I think you understand this already, but as it is the BUILD() is getting called linearly while the previous callback functions are still running. It's like you've created extra threads. One way to solve the problem would be to make BUILD a callback from the search function with the for loop in it. This would guarantee all functionality is complete before calling it.

This question might help implement the callback: Create a custom callback in JavaScript

2
  • But if I perform the for loop inside the search function, then call the callback which calls BUILD, isn't that the same thing? BUILD will still execute right after the for loop is done and before the callbacks inside the for loop are completed.
    – gruuuvy
    Commented Jun 30, 2012 at 17:41
  • Right now build isn't a callback from the search function, it's just the next action after the loop. I recently had a similar problem where my next line was executing before the function finished, but putting the next line in the callback made sure it happened when everything was done.
    – Rorrik
    Commented Jun 30, 2012 at 19:55

Not the answer you're looking for? Browse other questions tagged or ask your own question.