Doing a Hackathon to Get Your Project Across the Finish Line
September 20, 2019How to Use FlexBox for Your UI Layouts
October 16, 2019This is part two of my series on Asynchronous programming. In the previous post, I gave an overview of JavaScript’s nature regarding asynchronous programming, some of the solutions that have been presented over the years, and a brief look at the pros and cons of each. This post will focus on the original solution, the Callback.
Asynchronous Matters
Before we delve into the Callback and how it serves as a solution for asynchronous programming, I want to start this post by reiterating the reason for this aspect of programming we call asynchronicity.
Asynchronous matters. It is what allows us to use diverse technologies to work with each other in equally diverse ways by decoupling their unique timing and flow patterns into a continuous stream of data and logic. With it, we can have a blazing fast database system feeding data to a logic churning program that’s hosted separately and which releases aggregated information to rendered content on a browser thousands of miles away. This is something that is happening constantly in our present world with ease thanks to many technological factors, including asynchronous paradigms. Asynchronicity is the paradigm that encourages diversity of technology in the web space. It is one of the keys to the success and diversity of the World Wide Web.
Presently, JavaScript occupies both the browser and server side of the World Wide Web. In the browser, JavaScript is king, serving as the logical glue for the web page context, bringing meaningful functionality between the structure of HTML and the presentation of CSS. On the server, JavaScript is one of the many options for web servers. NodeJS is one of the more popular options today, particularly for API development. As the web has progressed, JavaScript has continued to serve well in this highly asynchronous space. Much of that is because of the nature of the language.
Ok Flex, But Weird
Now to the Callback. The essence of the Callback is found within the nature of the Function in JavaScript. Compositionally, the Function itself is a special form of the Object data type in JavaScript. The primary difference being that Function contains an additional internal “call” property that makes it active. The difference between Object and Function is easy to distinguish if you think of the Object as the noun in language and the Function as the verb.
The strength of Function in JavaScript stems from them being first-class citizens alongside native data types like String, Number, Boolean, and Object. This means that functions can be used in similar ways that we use other native data types. We can assign them to variables, pass them as functional parameters and return them from functions. It is this first-class nature that created the Callback solution for asynchronous programming.
The Callback, according to MDN, is:
“A callback function is a function passed into another function as an argument, which is then invoked inside the outer function to complete some kind of routine or action.”
Let’s use a simple example to illustrate:
Over-Simplified Example
function logInput(input) {
console.log(input);
}
function processInput(input, callback) {
return callback(input);
}
processInput('Bob', logInput);
In this example, we have two functions:
- logInput, which puts its “input” parameter into a console.log()
- processInput, which invokes its second parameter, “callback”, passing the first parameter, “input” to it.
At the end of the block, we invoke processInput, passing a string value, “Bob”, as the first parameter and logInput, the variable name of the function by the same name, as the second parameter. If you run this code in any JavaScript environment, you will see the console output of “Bob”.
Let’s return to our definition using this breakdown for better understanding:
“A callback function is a function (logInput) passed into another function (processInput) as an argument, which is then invoked inside the outer function (return callback(input)) to complete some kind of routine or action (console.log(input)).”
This example is intentionally as simple as possible. Practical, real world uses for Callbacks include SDK/API integrations. The Callbacks themselves contain all kinds of data management, error handling, and system processing.
Let’s try one more example, this one significantly more complex and slightly more realistic:
Reasonably Complex Example
function logCustomerPhoneNumber(customerName, callback) {
return getCustomer(customerName, getPhoneNumberFromCustomer, callback);
}
function getCustomer(customerName, callback1, callback2) {
//asynchronously call database for customer record
var customer = {
name: 'Bob',
phoneNumber: '555-555-1234'
};
return callback1(customer, callback2);
}
function getPhoneNumberFromCustomer(customer, callback) {
return callback(customer.phoneNumber);
}
function logValue(value) {
console.log(value);
}
logCustomerPhoneNumber('Bob', logValue);
This is a much more complex example. Here’s what’s happening:
- We invoke logCustomerPhoneNumber, passing it “Bob” and the Function logValue as its Callback
- logCustomerPhoneNumber returns the function getCustomer, with three parameters, customerName (“Bob”), getPhoneNumberFromCustomer (another Function), and our original callback (logValue)
- getCustomer (the function returned from logCustomerPhoneNumber) makes a pretend customer fetch for its first parameter, “Bob”. It returns an invocation of its second parameter, callback1 (aka getPhoneNumberFromCustomer) with two parameters of its own: customer (the Object we got for “Bob”) and callback2, which is our original Callback from the beginning, logValue.
- getPhoneNumberFromCustomer, the Function invoked at the end of getCustomer, then invokes its own Callback (logValue), passing it the phoneNumber property of the customer parameter (the Object made from “Bob”)
- logValue takes the value and passes it to a console.log().
- Result: if you run this code, you’ll see a console.log() for the value ‘555-555-1234’, the phoneNumber attribute of Bob’s customer object.
This example is unreasonably complex, combining mundane details into an integrated web of functions. But it also certainly communicates the advantages and pitfalls that Callbacks have.
The Callback Advantage
There are many advantages Callbacks provide including simplicity of each individual function and having a dedicated concern and straightforward process. The single purpose designation makes the code easy to test. The named functions make the code easy to debug because their errors will show up earmarked by their parent process in output logs.
Written correctly, we can reduce a large collection of processes like this one into single layer, single purpose functions – which is quite elegant.
Pitfalls
The single layer, single purpose is the exception, not the rule. Such code is not intuitive to write as one has to jump between functional contexts and ensure they are passing and invoking the right functions with the right things all the time. Instead, we tend to see two unfortunate patterns emerge with Callbacks:
- Anonymous (un-named) functions. This is used to make the code more declarative. Writing out the callback anonymously as the last parameter of a function is much easier to read than naming it and having to find said named function elsewhere in the code. Unfortunately, this reduces the easy-to-debug advantage that flat, named functions give us.
- The flying V pattern. Taken to extremes, developers will declaratively chain their callbacks into a V pattern, named after its progressively indented appearance. The trade-offs for this declarative approach are the same pitfalls of anonymous functions as well as sacrificed DRY (Don’t Repeat Yourself) principles.
It is pitfalls like this as well as asynchronous mechanisms found in other programming languages that caused the JavaScript community to collaborate on incorporating new asynchronous tools into the language. This is what we will explore in the next few posts.
However, it is worth noting that, when done well, Callbacks provide everything needed for asynchronous programming in a clean and stable way. They are the original and full-featured solution to an asynchronous world.