Passing functions as arguments in JavaScript — tips and pitfalls

Ciaran Morinan
4 min readOct 23, 2018

--

Functions in JavaScript are ‘first class’, which means they are treated like any other variable — including being passed to or returned from other functions.

When they’re passed as an argument to another function, they’re known as a ‘callback’ — to be called when the other function is ready for them.

Common examples of callbacks include functions provided to:

  • forEach or map to be called on each item in a collection
  • setTimeout or setInterval to be called after set time periods
  • then or catch to be called after a promise is settled

This may not always go as planned — this blogs touches on a few reasons why.

1. Pass functions — don’t invoke them

You can write callback functions entirely inside the function that needs them — but for ease of readability and debugging it often helps to declare or assign them elsewhere and reference them by function or variable name.

The first thing to watch out for is that you are actually referencing them by name — not invoking them. This is the difference between writing myFunction (returns a function) and myFunction() (executes it).

If you’re supplying a function as a callback to another function, it’s that other function’s job to execute it when it’s ready — not yours — i.e.

Here setTimeout is looking for the first argument to provide a ‘thing to do when you are ready’. If we call sayBoo() rather than referencing it, what we are actually providing for setTimeout to do is whatever sayBoo() returns — in this case, nothing it can execute. If sayBoo() returned a function, then that function would be available to setTimeout.

There may be instances in which you want to actually call a function instantly, and have it return another function to be used as a callback — but if you’re not doing this, and your callbacks aren’t behaving as expected, check that you’re only referencing a function name (or variable name you’ve stored a function in), and aren’t calling it in place!

2. How to pass arguments properly with callbacks

We need to think differently about how we pass arguments to callback functions. We can’t supply a function name with arguments directly in brackets — this will lead to it being executed immediately, rather than being left as a callback when the surrounding function is ready for it.

For example, if we wanted to make a countdown that called itself:

…we can’t provide countdown(--n) in this way, because it is called as soon as line 4 is evaluated, rather than being called by setTimeout when it’s ready. Adding the () parentheses (with or without arguments inside them) means we’re executing the function in place, rather than passing it as an argument.

setTimeout and setInterval actually have their own way of dealing with this — you can provide a third argument (and as many more as you like) to either, which will be passed to the callback function [not supported by IE9].

Or you could provide a short anonymous function as the callback, which then itself executes the named function when it’s called upon. Both seen here:

For functions like forEach, map, and then, which have ready-made arguments to be passed into their callback functions (e.g. the current value in a collection being iterated over, or the value of a resolved promise), you can take advantage of JavaScript’s ability to use tacit or ‘point-free’ programming.

This involves specifying a function as a callback without naming the arguments to be passed to it — the calling function will pass whatever values it has available as arguments, for the callback to deal with however it can.

In the example above, we have a function saySquared that takes one argument. forEach actually has three to offer — the current value, the current index, and the original collection — but JavaScript functions don’t complain if you supply them with more arguments than they need, and so as long as you know the order in which arguments will be supplied, you can use the style effectively.

If you’re not sure what arguments will be supplied by a function in what order, check the documentation. As a visual aid you can also pass console.log as a callback to a function using the tacit style, omitting any other instruction — it will happily take any number of arguments supplied and print them for you.

It won’t always be appropriate to use tacit programming — in some cases it might make it harder for a reader (whether someone else or future-you) to understand what’s going on, especially if they aren’t familiar with the arguments a function is supplying to a callback, or if your callback is only making use of some of the available values.

3. Getting ‘this’ right when passing functions

A third thing to bear in mind is how using functions as callbacks changes the context in which they are invoked — if your function relies on the this keyword to refer to the context in which you originally wrote it, you may find that invoking it as a callback from within another function alters what this refers to— normally reverting to the global object/window.

There are various ways of dealing with this — defining a short anonymous function as the callback which in turn calls your named function will often resolve the issue. You can also use bind to specify the context you want the method bound to. Both shown here:

If you’re using an object constructor you can also bind its methods to it as soon as you construct it, so that you don’t have to worry about it again:

Increasingly, with newer JavaScript syntax, declaring functions with arrow syntax will help: they will automatically bind ‘this’ to the scope in which the function is declared.

There are lots of resources out there on this issues and this is far from an exhaustive discussion. They vary depending on what you’re declaring, where you’re declaring it, where you’re calling it, and what syntax you’re using — so watch out for them!

--

--