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.