Premature Invocation (or "Not Grokking Callbacks")

Often on Q&A forums you'll see code snippets like

window.onload = letsDoThis()

or

google.setOnLoadCallback(plotMyCharts(chartData))

or

$('#my-button').click(console.log('You clicked me!'))

All of these have the same problem, which I shall call - Premature Invocation, i.e. code is run before it's intended to.

Functions. Callbacks.

Before explaining what's wrong with the 3 above code snippets, let's take a step back and take a look at functions and the use of them as callbacks.

This is a function

function hello(){
    return "Hello world!"
}

Saying hello by itself gets you back the function

hello  // gives the function `hello` again

You call the function like this

hello() // gives "Hello world!"

The addition of the paranthese () to the right of the function name invokes the function immediately. This is important.

This function can be assigned to a variable like any other value,

var justAnotherFunction = hello

and can be invoked via their new handle too!

justAnotherFunction()

they can also be passed to other functions

setTimeout(hello, 1000) // Will call `hello` a second later

or attached to objects

window.onload = hello   // Will call `hello` when page loads

In both of last two examples, the function is said to be a callback. A callback is usually used for time-delayed events or asynchronous events.

Don't call me. I'll call you.

They are callbacks in the sense that we give up control of when and how the function will be called, if it gets called at all.

Note: in both callback examples, we do not invoke hello directly(there are no parentheses to the right of it), but rather, it gets invoked indirectly - by the taker of our function. Take a deep breath. It's okay.

Callbacks as Strings

In defense of programmers who make this mistake, there's is something about browser programming that contributes greatly to this confusion - the fact that in various places, callbacks can also be given as strings instead of functions, as a short-hand. One such example is setTimeout - the first argument of setTimeout can optionally be a string containing Javascript

setTimeout("hello()", 1000)

Note: this code is severely different from setTimeout(hello(), 1000) - which as we already discussed, will execute hello immediately. When you put quotes around hello() as the example above, it will instead be equivalent to wrapping an inline anonymous function around the code in the string

setTimeout(function()
    hello()
}, 1000)

Another place this happens is when you write callback handlers as tag attributes in your markup, like this

<body onload="letsDoThis()">

or this

<a id="a" href="#" onclick="someFunc(3.1415926); return false;">
    Click here!
</a>

that gets converted to

window.onload = function(){
    letsDoThis()
}

and

a.onclick = function(){
    someFunc(3.1415926); return false;
}

respectively. You cannot assign a string to a handler like that in Javascript though, i.e. this

window.onload = "letsDoThis()"

will not work.

Solutions to Premature Invocation

Now, let's come back to the 3 problematic code snippets.

window.onload = letsDoThis()

Now do you know what to do? That's right, let go of those parentheses and be free!

window.onload = letsDoThis

Okay, now let's look at the second one

google.setOnLoadCallback(plotMyCharts(chartData))

This one is trickier because we need to pass a parameter to the plotMyChart, we can't just do

google.setOnLoadCallback(plotMyCharts)

because then google.setOnLoadCallback won't know to supply our plotMyChart with the chartData. So, what do we do?

google.setOnLoadCallback(function(){
    plotMyCharts(chartData)
})

The solution is to introduce a new, unnamed function that wraps around the code we want to execute. Now, the unnamed function can be called without any parameters, and yet plotMyCharts is supplied with the correct chartData whenever our unnamed function is called(btw, this works because a nifty thing called closure).

The function being unnamed isn't a requirement though, you just as well could have named it

function onGoogleLoad(){
    plotMyCharts(chartData)
}
google.setOnLoadCallback(onGoogleLoad)

On to example 3

$('#my-button').click(console.log('You clicked me!'))

You got this right?

$('#my-button').click(function(){
    console.log('You clicked me!')
})

Great! You grokked callbacks, and may you never prematurely invocate ever again.

blog comments powered by Disqus