When writing a callback, when is best to have the callback return a value, and when is it best to have the callback modify a parameter? Is there a difference?
For example, if we wanted to grab a list of dependencies, when would you do this:
function GetDependencies(){
return [{"Dep1" : 1.1},
{"Dept2": 1.2},
{"Dep3" : 1.3}];
}
And when would you do this?
function RegisterDependencies(register){
register.add("Dep1", 1.1);
register.add("Dep2", 1.2);
register.add("Dep3", 1.3);
}
2
Since your example uses a JavaScript-like language (duck-typed), my answer will be specific to duck-typed languages.
From a usability standpoint, parameters are a tad more self-documenting then return values. If someone was crawling through code and saw:
function callback( model, rootElement )
{
//TODO: implement editor UI
}
It’s a bit more obvious what should be done then:
function callback( model )
{
//TODO: implement editor UI
// ... okay, where?
}
In addition, curious programmers have a bit more to work with if given parameters. For example, imagine an Options object with 20 configurable options. Providing that Options object as a parameter (pre-loaded with defaults) gives the programmer a chance to poke into the object and explore a bit:
function callback( options )
{
console.log( options );
}
While a callback that must return an options object requires the programmer to find your documentation:
function callback()
{
// umm... return what?
}
In addition, if you want a value returned but the user doesn’t return a value, what happens? I’ve seen many cases where this condition has produced “cannot read property of undefined” errors… which usually breaks the page.
If the user is handed a parameter and given permission to modify it, then you aren’t depending on them doing anything. For example, perhaps the user just wants to log that something has happened and your callback is a convenient place to do that. Requiring that they return something just makes the code more complicated.
function setOptionsCallback( options )
{
console.log( 'object created' );
}
Versus:
function setOptionsCallback()
{
console.log( 'object created' );
return {};
}
In the end, though, most of these problems can be overcome with some good defensive programming and solid documentation.
Callbacks fall into the following broad categories:
-
Those that produce a side effect but the dispatch mechanism doesn’t care about the side effects. They usually don’t return anything. Everything they need to operate properly are provided through arguments to the function. That may involve modifying the contents of the arguments as well.
-
Those that may or may not produce a side effect but the dispatch mechanism cares whether they succeeded or not. These functions usually return an integer that indicates the status of the callback function’s execution. Callback functions that fall into this category also expect that everything they need to operate properly are provided through arguments to the function. That may involve modifying the contents of the arguments as well.
-
Those that return more complex or different data than the first two are unusual. I don’t recall having seen any in my work or other places. If you are contemplating using callbacks of this category, you probably have thought through this well and your team, if you are in a team, is convinced this is the correct approach.
If you haven’t thought through this, I would go with using callback functions that belong to the first two categories.
I don’t like functions modifying my variables so I prefer to use the return value and then I can decide what to do with that value. Occasionally it is convenient to say return a boolean indicating if a value was modified and have the function modify the value. That can save time and code but in general I would prefer to drop those two pieces of information in a container and return that.
1
Short Answer:
When you have to call a lot of functions down the line, it better callbacks modify state of an object, rather than return something.
When you don’t really need to call a lot of functions, its okay if the callback return something back.
Long Answer:
When the framework in concern is involved with dispatching a chain of messages (or in other words, call a lot of routines/functions) which control the application’s behaviour, then we’d go for modifying the value of a variable or an object’s state rather than returning something back.
A good example of this is windows forms.
A detailed article about it will be linked to later.
In brief however,
- windows forms is a framework which abstracts all the low-level message handling, characteristic of a gui-based app.
- Through out the application’s lifetime the app receives messages from the OS (reads it off a queue) and sends messages (to the OS).
-
Usually when a message is consumed, it results in an “event” in our application.
- In classic windows application (using pure API or winapi) we only concern about some messages (like close, click, etc).
- The windows forms framework however handles “all” the messages.
- A windows forms app typically subscribes to a subset of the events; so essentially the apps responds to a subset of the messages
Most of these events modify the state of an object passed to it as argument based on which the framework decides how to respond. The framework needs to send messages to the OS, and receive messages from the queue. There are standard functions for this (defined by the API of course).
In this case, reading the state of a mutated object (and then deciding what message to send to the OS) serves to be “faster”. (Imagine this in terms of how pure assembly instructions would execute).
Now when would you actually want to use a return?
In a few words, I’d say, when you don’t have to call so many different functions down the line. Good example is jQuery’s each iterator.
$.each([1, 2, 3, 4], function foo(_, v) {
if(v === 2) return false;
console.log(v);
});
In this case jQuery simply calls function foo
(name given for reference sake), and not anything else, so a callback returning something is “affordable” here. It also makes up for better understanding of code.