3

The Good and Bad of JavaScript Callbacks

 2 years ago
source link: https://blog.bitsrc.io/the-good-and-bad-of-javascript-callbacks-dc6dffc21519
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

The Good and Bad of JavaScript Callbacks

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:

  1. Get the user information.
  2. Get the list of order IDs for the user
  3. Get the list of products for each order
  4. 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.

Promise Comes to the Rescue of the Callback Hell

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.

Bitoffers 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 →

An independently source-controlled and shared “card” component. On the right => its dependency graph, auto-generated by Bit.

Learn More


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK