This content originally appeared on Bits and Pieces - Medium and was authored by Daijue Tang
Callbacks are functions that are called back later. If you think I didn’t explain anything in the previous sentence, I did emphasize one important identity of a callback — a function. The callback function — including its references — is passed as an argument to other functions. The functions that receive the callback function as a parameter are the ones responsible to call back the callback function. Callbacks are great because they open up a lot of programming possibilities.
Synchronous Callbacks
Many people, especially those who are old school, had their first encounter with callbacks when they learned that different comparison functions can be supplied to the same sorting algorithm. For example, when using the Array.prototype.sort() method to sort an array of integers, an optional parameter is the comparison function compareFn.
let arr1 = [22, 25, 55, 66, 23, 15, 1, 12]
arr1.sort() // arr1 is sorted in ascending order
// arr1 becomes [1, 12, 15, 22, 23, 25, 55, 66]
let arr2 = […arr1] // deep copy arr1 to arr2
arr2.sort((e1, e2)=> e2-e1) // arr2 is sorted in descending order
// arr2 becomes [66, 55, 25, 23, 22, 15, 12, 1 ]
// (e1, e2)=> e2-e1 is the comparison function
This type of callbacks are available in most programming languages, achieving the effect of polymorphic algorithms.
The above illustrates how synchronous callbacks work. Synchronous callbacks are executed inside the high-order functions that use them. When the high-order function is done executing, execution of its callback arguments also completes. Since the high-order function has to wait for the completion of synchronous callback execution, synchronous callbacks are also referred to as blocking callbacks — the execution of callbacks block the execution of their caller functions.
Some additional examples of synchronous callbacks in JavaScript are methods for iterating arrays: forEach, map, filter, reduce, some, every , etc.
let arr = [1,2,3,4,5]
let arrDoubled = arr.map(e=>e+e)
console.log(arrDoubled) // output [ 2, 4, 6, 8, 10 ]
Asynchronous Callbacks
If synchronous callbacks are ways to achieve greater programming flexibility, then asynchronous callbacks are ways to achieve greater performance and user experience. The power of asynchronous callbacks lie in JavaScript’s unique runtime model. JavaScript is a single threaded language, meaning that the execution engine of JavaScript only has one call stack.
Then how does asynchronous execution achieved?
The magic is in the API handlers of the JavaScript runtime environment. In the case of web browsers, the APIs are the web APIs; in the case of Node.js, the APIs are the I/O APIs. Tasks of executing asynchronous callbacks are put into the callback queue.
After existing code in the call stack runs to its completion, event loop — a process that is part of the JavaScript engine — brings the callbacks in the callback queue to execution.
Once a callback is run by the execution engine, it is again run to its completion (until the call stack is empty) before the next callback starts to run. This run-to-completion for code on the call stack continues until all the callbacks in the queue are executed.
The asynchronous aspect comes from the fact that a callback is not executed immediately in the high order function, but is put in the callback queue to wait for its turn to be run on the call stack.
The high order function is the one to dispatch the callback task, but not the one to run it.
The most prevalent example of using an asynchronous callback is using the setTimeOut method.
console.log("before setTimeout")
setTimeout(
()=>{ console.log("result is here after 2 seconds") },
2000
)
console.log("after setTimeout")
This method is in the global object window in the case of browsers and global in the case of Node.js.
The first parameter is the callback function, the second parameter is the time to wait in milliseconds. setTimeout does not need to wait for the callback to complete before it returns.
Here is how the output looks like:
before setTimeout
after setTimeout
result is here after 2 seconds
The benefit of the asynchronous callback mechanism is that all the synchronous code is not blocked by asynchronous events. Asynchronous events (e.g. AJAX requests to remote servers) potentially can take some time to run. Web applications can run more smoothly and responsively with asynchronous callbacks.
Callback Hell
You must have heard the term “callback hell” before. It sounds scary, but in reality is just aesthetically not that appealing and functionally prone to error and hard to debug and maintain.
Imagine an e-commerce web application. One of the functions provided by the website is to obtain detailed history of order information for the given customer. There are several steps needed to obtain the required information:
- Get the user information.
- Get the list of order IDs for the user
- Get the list of products for each order
- Compile the information obtained in the previous steps into a report
Translate into code — callback hell style:
Note that setTimeout is used to simulate querying databases to get the corresponding data. The indented callbacks here may not seem to be too bad, but in practice the level of callbacks can reach to a much bigger number. With the presence of other control logic and error handling, the code for sure will go out of the screen.
There is Always Promise
To address the problem described in the previous section, ES6 introduced Promise as JavaScript’s first level constructor, just like String, Date, Array, etc.
Here is how to use promises to resolve the callback hell issue:
Note how the asynchronous requests to the databases (simulated with setTimeout) are wrapped inside a Promise constructor.
Also note that for getUserInfo, getOrderInfo, getProductInfo functions, callbacks are no longer needed.
In the end, each step returns a Promise object and the results are chained through the then methods.
All the steps become a vertical chain of promises versus a horizontal stretch of callbacks.
Stay tuned for my next article explaining the details of Promise and its inner workings.
Build with independent components for speed and scale
Instead of building monolithic apps, build independent components first and compose them into features and applications. It makes development faster and helps teams build more consistent and scalable applications.
Bit offers a great developer experience for building independent components and composing applications. Many teams start by building their Design Systems or Micro Frontends, through independent components.
Give it a try →
Learn More
- Building a React Component Library — The Right Way
- 7 Tools for Faster Frontend Development in 2022
- The Composable Enterprise: A Guide
The Good and Bad of JavaScript Callbacks was originally published in Bits and Pieces on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Bits and Pieces - Medium and was authored by Daijue Tang
Daijue Tang | Sciencx (2022-03-10T08:51:23+00:00) The Good and Bad of JavaScript Callbacks. Retrieved from https://www.scien.cx/2022/03/10/the-good-and-bad-of-javascript-callbacks/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.