Krohn Krohn - 2 months ago 7
jQuery Question

Why does Javascript say "callback is not a function" in setTimeout when the wait value is much lower?

I have written a basic job runner in javascript (utilizing some JQuery too, but that's another story for another day) and I came across this queer Issue:

The method I run to wait for all jobs to complete:

$.getAllProducts = function(callback){
$.getProductDetails('|ALL|', function(allProductsResult){ //intentionally
var objAllProducts = JSON.parse(JSON.parse(allProductsResult));
var objProductsBuiltUp = {};

var productLength = objAllProducts.length;
$.totalJobs(productLength);

var processed = 0;
$.completedJobs(processed);

$.each(objAllProducts, function(i,v){
$.getProductDetails(objAllProducts[i].ProductCode, function(result){
$.mergeShallow(objProductsBuiltUp, JSON.parse(JSON.parse(result)));
processed++;
$.completedJobs(processed);
});
});

$.wait(0, false, function(isDone){ //allow at least 50ms wait time, otherwise this confuses javascript into thinking there are no callbacks
if (isDone){
callback(objProductsBuiltUp.ProductComponents);
}
});
});
}


The handlers for the job

$.checkProgress = function() {
return $.jobs === $.completed;
}

$.totalJobs = function(total) {
$.jobs = total;
}

$.completedJobs = function(completed) {
$.completed = completed;
}

$.wait = function(timeout, debug, callback) {
setTimeout(function() {
if (debug) {
console.log($.completed + " / " + $.jobs + " = " + ($.completed / $.jobs * 100) + "%");
}

if ($.checkProgress() == false) {
$.wait(timeout, debug);
}
callback($.checkProgress()); // <-- complaining one

}, timeout);
}


This is the key-point code for my little job runner, other methods will call
$.totalJobs()
to set the amount of jobs that need to be performed (normally based on amount different calls need to be made to an API In my scenario), and
$.completedJobs()
- which is called when the payloads are returned in the API handler's callbacks

The issue is, when I set my "Waiter" to 50ms, I don't get any errors whatsoever, and the method performs as per expected.

When I set it to low values like 5ms, 1ms, 0ms, It tells me:


"xxxxx.helpers.js:48 Uncaught TypeError: callback is not a function"


Anyone have a wild-flung theory why this would occur? it is, afterall, only a glorified
setTimeout
.

(P.S. In response to why I use JQuery global methods and variables to store info is to make using Meteor easier on myself knowing it's loaded into 1 place - this is the platform I am developing on at the moment.)

EDIT was better for me to add the whole method where the callback is run

Answer

It looks like you're not passing a callback here:

$.wait = function(timeout, debug, callback) {
    //code here
    if ($.checkProgress() == false) {
        $.wait(timeout, debug); // === $.wait(timeout, debug, undefined);
    }
    callback($.checkProgress()); // <-- complaining one

}, timeout);

so if $.checkProgress() is false, you're calling $.wait recursively only callback is undefined...

At first glance, I think what you wanted to write there was:

$.wait(timeout, debug, callback); // pass callback argument to inner call

But then obviously, you wouldn't want to invoke the callback multiple times:

$.wait = function(timeout, debug, callback) {
    //code here
    if ($.checkProgress() == false) {
        $.wait(timeout, debug, callback);
    } else {
        callback($.checkProgress());
    }

}, timeout);

The reason why the line you marked as "the complaining one" is in fact complaining is because it's the recursive call. $.checkProgress evaluated to false, the $.wait function is invoked (this time, callback is undefined), and that continues until $.checkProgress() === false evaluates to false. Then, callback (which is undefined) will get invoked in the inner call.


This issue started appearing when the interval was reduced down. That makes sense, because you're only recursively calling $.wait if the jobs hadn't been completed. The higher the timeout/interval, the greater the chance the jobs were completed the first time around.

By reducing the interval, you arrived at a point where $.wait got invoked before the jobs had finished, and you entered the $.checkProgress() === false branch, calling $.wait without passing the callback (essentially losing a reference to it).

By the time the jobs had completed, you were trying to invoke callbackm which was set to undefined.

Comments