Patterns And Best Practices In JavaScript: Dealing With Callback Functions
Tags: javascript, webdev, programming, beginners
Published: 2022-12-12 20:08:00

**As in any other programming language, JavaScript has a number of best practices and associated bad practices. Due to its dynamic properties, JavaScript also has various pitfalls.**

[Paul Knulst](https://www.paulsblog.dev/)  in  [Programming](https://www.paulsblog.dev/tag/programming/) • 4 min read

* * *

As in any other programming language, JavaScript has a number of best practices and associated bad practices. Due to its dynamic properties, JavaScript also has various pitfalls.

Callback functions are functions that are passed as parameters to other functions and called by them. They are a common design pattern in asynchronous JavaScript development. The basic (not yet optimal) structure of this design pattern is as follows:
```javascript
function doStuff(callback) {
  /* ... */
  callback();
  /* ... */
}
```

The `doStuff()` function expects a function as a parameter and calls it at a specific point in time:
```javascript
function justLogSomething() {
  console.log('callback called');
}
doStuff(justLogSomething);  // Output: "callback called"
```

Check The Callback Function Type
--------------------------------

Due to the weak typing of JavaScript, a function that expects a (callback) function as a parameter can in principle also be passed any other value (or no value at all). However, calling this supposed function inevitably leads to a type error ("callback is not a function"). Based on the example above, the following calls would be possible, but not advisable:
```javascript
doStuff(1337);              // type error -> number
doStuff('Paul Knulst');     // type error -> string
doStuff();                  // type error -> undefined
```

For this reason, it is important to first check the type of the callback parameter inside the function and make sure that it really is a function. This can be achieved using the `typeof` operator, as shown in the following listing. If this returns the value "function" for the passed parameter, it is a function and nothing stands in the way of calling it:

```javascript
function doStuff(callback) {
  /* ... */
  if(typeof callback === 'function') {
    callback();
  }
  /* ... */
}
```

If there are several places within a function where the callback function could be called, this check would have to precede all these places:

```javascript
function doStuff(callback) {
  /* ... */
  if(someBool) {
    /* ... */
    if(typeof callback === 'function') {
      callback();
    }
  } else {
    /* ... */
    if(typeof callback === 'function') {
      callback();
    }
  }
  /* ... */
}
```

This can be avoided if, as in the following listing, you carry out the check at the beginning of the function and, in the event that the callback parameter is not a function, simply redefine it with an anonymous empty function. This simple but efficient trick secures all the following calls to the callback function in one fell swoop:

```javascript
function doStuff(callback) {
  if(!(typeof callback === 'function')) {
    callback = function() {};  // redefinition
  }
  /* ... */
  if(someBool) {
    /* ... */
    callback();
  } else {
    /* ... */
    callback();
  }
  /* ... */
}
```

With the help of the conditional operator, the whole thing can even be reduced to one line of code:

```javascript
callback = (typeof callback === 'function') ? callback : function() {};
```

Parameters Of A Callback Function
---------------------------------

Since callback functions are called asynchronously and can neither supply a direct return value nor throw errors, appropriate parameters should be provided for at least these two cases:

1.  one parameter that contains information about the error that occurred in the event of an error,
2.  one parameter that normally contains the result of the asynchronous calculation.

Even if the order of these two parameters does not matter in principle, it has become a convention - especially when developing Node.js modules - to list the error as the first parameter and the result as the second parameter (if there is no error, the first parameters corresponding to zero).

```javascript
doStuff(function(
  error,            // first parameter: error object
  result            // second parameter: result object
  ) {
});
function doStuff(callback) {
  /* ... */
  var result = null;
  try {
    // code that throws error
  } catch(error) {
    callback(error);
  }
  callback(null, result);
}
```

You can use a simple if query within the callback function to find out whether an error has occurred:

```javascript
doStuff(function(error, result) {
  if(error) {
    // error handling
  } else {
    // normal workflow
  }
});
```

Return Of The Callback Call
---------------------------

In some cases, calling callback functions as shown in the previous examples can lead to unintended program behavior. For example, in the penultimate listing, the callback function is called twice in case an error occurs.

Here is the code again:

```javascript
function doStuff(callback) {
  /* ... */
  var result = null;
  try {
    // code that throws error
  } catch(error) {
    callback(error);
  }
  callback(null, result);
}
```
    

The problem here is that the code after the try-catch block will always be called. Even if the catch block was previously jumped due to an error and the callback function was called there. This is a fact that you can quickly overlook when just reading the source code. For this reason, you should place a "return" before each call of a callback function, which jumps directly out of the calling function.

```javascript
function doStuff(callback) {
  /* ... */
  var result = null;
  try {
    // code that throws error
  } catch(error) {
    return callback(error, null);
  }
  return callback(null, result);
}
```

The prerequisite for this is, of course, that the calling function is structured accordingly and the call of the callback function always represents the end of the function.

For example, the following snippet shows the wrong way to do it:

```javascript
function doStuff(callback) {
  /* ... */
  var result = null;
  try {
    // code that throws error
  } catch(error) {
    return callback(error, null);
  }
  return callback(null, result);
  // the code below will never be executed
  if(foo) {
    /* ... */
  }
}
```

The Execution Context Of The Callback Function
----------------------------------------------

Particular caution is required when passing functions as callback parameters that access the execution context via `this`. In this case, you have to take advantage of the `bind()` function and use it to create a new function that is bound to the desired execution context.

Then this new function is passed as a callback parameter:
```javascript
var person = {
  name: 'Paul Knulst', 
  printName: function() {
    console.log(this.name);
  }
}
doStuff(person.printName);  // WRONG

var printNameBound = person.printName.bind(person);
doStuff(printNameBound);    // CORRECT
```

Conclusion
----------

When working with callback functions, several best practices should be considered, including type checking, the order of callback parameters, calling callback functions once, and setting the execution context.

The fundamental question of whether to use promises, generator functions or the async/await combination for asynchronous programming instead of callback functions will be discussed in the following articles.

This article was originally published on my blog at [https://www.paulsblog.dev/patterns-and-best-practices-in-javascript-dealing-with-callback-functions/](https://www.paulsblog.dev/patterns-and-best-practices-in-javascript-dealing-with-callback-functions/)

Feel free to connect with me on [my blog](https://www.paulsblog.dev), [Medium](https://medium.knulst.de), [LinkedIn](https://www.linkedin.com/in/paulknulst/), and [Twitter](https://twitter.com/paulknulst).

* * *

🙌 Support this content
-----------------------

If you like this content, please consider [supporting me](https://www.paulsblog.dev/support/). You can share it on social media or [buy me a coffee](https://buymeacoffee.com/paulknulst)! Any support helps!

Furthermore, you can [sign up for my newsletter](https://www.paulsblog.dev/newsletter/) to show your contribution to my content. See the [contribute page](https://www.paulsblog.dev/contribute/) for all (free or paid) ways to say thank you!

Thanks! 🥰

* * *

Photo by [Carl Heyerdahl](https://unsplash.com/@carlheyerdahl?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) / [Unsplash](https://unsplash.com/s/photos/technology-software-deve?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)