This article is aimed at people starting out with asynchronous coding in javascript so we would keep things simple by avoiding big words, arrow functions, template literals etc.
Callbacks are one of the most used concepts of modern functional javascript and if you’ve ever used jQuery, chances are you’ve already used callbacks without even knowing (we will get back to it in a minute).
What the Heck are Callback Functions?
A callback function in its simplest terms is a function that is passed to another function, as a parameter. The callback function then gets executed inside the function where it is passed and the final result is returned to the caller.
Simple right? Now let us implement a callback function to get scores on levelling up in an imaginary game.
Once inside startGame()
function, we call the levelOne()
function with parameters as currentScore and our callback function().
When we call levelOne()
inside startGame()
function’s scope, in an asynchronous way, javascript executes the function levelOne()
and the main thread keeps on going ahead with the remaining part of our code.
This means we can do all kind of operations like fetching data from an API, doing some math etc., everything which can be time-consuming and hence we won’t be blocking our main thread for it. Once the function(levelOne()
) has done with its operations, it can execute the callback function we passed earlier.
This is an immensely useful feature of functional programming as callbacks lets us handle code asynchronously without us have to wait for a response. For example, you can make an ajax call to a slow server with a callback func. and completely forget about it and continue with your remaining code. Once that ajax call gets resolved, the callback function gets executed automatically.
But Callbacks can get nasty if there are multiple levels of callbacks to be executed in a chain. Let’s take the above example and add a few more levels to our game.
Wait, what just happened? We added two new functions for level logic, levelTwo()
and levelThree()
. Inside levelOne’s callback(line #22), called levelTwo() function with a callback func. and levelOne’s callback’s result. And repeat the same thing for levelThree() function again.
Now just imagine what this code will become if we had to implement the same logic for another 10 levels. Are you already panicking? Well, I am! As the number of nested callback functions increases, it becomes tougher to read your code and even harder to debug.
This is often affectionately known as a callback hell. Is there a way out of this callback hell?
I Promise there’s a better way
Javascript started supporting Promises from ES6. Promises are basically objects representing the eventual completion (or failure) of an asynchronous operation, and its resulting value.
Let us try to rewrite our callback hell example with promises now.
We have re-wrote our level(One/Two/Three) functions to remove callbacks from the function param and instead of calling the callback function inside them, replaced with promises.
Once startGame is resolved, we can simply call a .then()
method on it and handle the result. We can chain multiple promises one after another with .then() chaining
.
This makes the whole code much more readable and easier to understand in terms of what is happening, and then
what happens next and so on.
The deep reason why promises are often better is that they’re more composable, which roughly means that combining multiple promises “just works” while combining multiple callbacks often doesn’t.
Also when we have a single callback versus a single promise, it’s true there’s no significant difference. It’s when you have a zillion callbacks versus a zillion promises that the promise-based code tends to look much nicer.
Okay, we’ve escaped successfully from the callback hell and made our code much readable with promises. But what if I told you there’s a way to make it cleaner and more readable?
(a)Wait for it
Async- await is being supported in javascript since ECMA2017. They allow you to write promise-based code as if it were synchronous code, but without blocking the main thread. They make your asynchronous code less “clever” and more readable.
To be honest, async-awaits are nothing but syntactic sugar on top of promises but it makes asynchronous code look and behaves a little more like synchronous code, that’s precisely where it’s power lies.
If you use the async
keyword before a function definition, you can then use await
within the function. When you await
a promise, the function is paused in a non-blocking way until the promise settles. If the promise fulfils, you get the value back. If the promise rejects, the rejected value is thrown.
Let us see now how our game logic looks once we rewrite it with async-awaits!
Immediately our code becomes much more readable but there’s more to Async-await.
Error handling is one of the top features of Async-await which stands out. Finally we can handle both synchronous and asynchronous errors with the same construct with try and catches which was a pain with promises without duplicating try-catch blocks.
The next best improvement from good old promise world is code debugging. When we write arrow function based promises, we can’t set breakpoints inside our arrow functions so debugging is tough at times. But with async-awaits, debugging is just like how you would do a synchronous piece of code.
I’m sure that by now you have a better understanding of asynchronous programming in javascript. If you have a question, let me know below. If you found this helpful, give me a shoutout on Twitter!
Happy Coding!