Callbacks and higher-order functions are some of the most misunderstood concepts in JavaScript. In this post, we will become familiar with them to write pro-level code as JavaScript engineers.
A callback function is a function passed into another function as an argument, which is then invoked inside the outer function to complete some sort action.
Any function that takes in a function or returns one out is a higher-order function. This is just a term to depict these functions - we call any function that does that a higher-order function - yet there’s nothing unique about them inherently.
Before continuing, let’s ask ourselves a question.
Let’s see why…
Takes no input
Returns 10*10
1
2
3
4
function tenSquared() {
return 10 * 10;
}
tenSquared() // 100
What principle are we breaking? DRY (Don’t Repeat Yourself )
👉 We can generalize the function to make it reusable.
1
2
3
4
5
6
function squareNum(num) {
return num * num;
}
squareNum(10); // 100
squareNum(9); // 81
squareNum(8); // 64
Parameters (placeholders) mean we don’t need to decide what data to run our functionality on until we run the function. We then provide an actual value (argument) when we run the function. Higher-order functions follow this same guideline; we might not choose exactly which part of our functionality it is until we run our function.
Now suppose we have a function copyArrayAndMultiplyBy2.
1
2
3
4
5
6
7
8
9
function copyArrayAndMultiplyBy2(arr) {
const output = [];
for (let i = 0; i < arr.length; i++) {
output.push(arr[i] * 2);
}
return output;
}
const myArray = [1, 2, 3];
const result = copyArrayAndMultiplyBy2(myArray)
What if you want to copy the array and divide it by 2?
What principle are we breaking? DRY (Don’t Repeat Yourself )
👉 We could generalize our function so we pass in our specific instruction only when we run copyArrayAndManipulate!
1
2
3
4
5
6
7
8
9
10
11
12
function copyArrayAndManipulate(arr, instructions) {
const output = [];
for (let i = 0; i < arr.length; i++) {
output.push(instructions(arr[i]));
}
return output;
}
function multiplyBy2(input) {
return input * 2;
}
const result = copyArrayAndManipulate([1, 2, 3], multiplyBy2);
Functions in Javascript = first-class objects. In addition to this, let’s highlight some of the other features of functions:
They can co-exist with and can be handled like other any Javascript object
Assigned to variables and properties of other objects
Passed as arguments into functions
Returned as values from functions
Consider this piece of code
1
2
3
4
5
6
7
8
9
10
11
12
function copyArrayAndManipulate(arr, instructions) {
const output = [];
for (let i = 0; i < arr.length; i++) {
output.push(instructions(arr[i]));
}
return output;
}
function multiplyBy2(input) {
return input * 2;
}
const result = copyArrayAndManipulate([1, 2, 3], multiplyBy2);
The outer function that takes in a function is our higher-order.
The function we insert is our callback function.
Callbacks and higher-order functions simplify our code and keep it DRY.
Declarative readable code: Map, filter, reduce - the foremost readable way to write code to work with data.
Pro interview prep: One of the most popular topics to check in an interview both for mid/senior-level job interviews.
Asynchronous JavaScript: Callbacks are a central part of async Javascript, and are under the hood of promises, async/await.
To understand asynchronous Javascript we ought to know how synchronous Javascript works. Here’s a reminder of how Javascript executes code.
1
2
3
4
5
6
7
const num = 3;
function multiplyBy2(inputNumber) {
const result = inputNumber * 2;
return result;
}
const output = multiplyBy2(num);
n this block, the lines are executed one after the other:
We start by assigning num a value of 3 in global memory
Define multiplyBy2 : and give it a parameter of inputNumber:
1.Define constant result and assign the value of input number multiplied by 2 which is 6
2. We return out the value assigned to label result
Store the returned value in output label
Javascript is a synchronous language that means we do each line, we finish it, and when we finish that we go to the next line.
Let’s suppose we have a task of speaking to Instagram’s server to get new posts so that when we click on view posts it will send a message to Instagram’s server. On those messages, the data coming back from Instagram is going to want to display that new data, those new posts. We’re going to sit on that line and wait for that data to come back, and when it finally comes back, then we can display it and move on to the next line. But that could take a half second. Or, it could take longer and in that time we can’t run any further code afterwards. That’s a serious problem. We need to prevent our codes from being blocked by other lines of codes.
Take this example:
1 2 3 4 5
const posts = getPosts("https://www.instagram.com/blessingartcreator/1") // ⛔350ms wait while a request is sent to Instagram HQ displayPosts(posts) // more code to run console.log("I want to executeeee!")
Asynchronicity is the backbone of modern web development in JavaScript yet…
**JavaScript is: **
- Single threaded (one command runs at a time)
- Synchronously executed (each line is run in the order the code appears)
**So what if we have a task: **
- Accessing Instagram’s server to get new posts that takes a long time
- Code we want to run using those posts
Challenge: We want to wait for the posts to be stored in posts so that they’re there
to run displayPosts on - but no code can run in the meantime.
If you ever run a function like getPosts, it sends off a message to Instagram and when the posts come back, we store them in the label posts, then we display those posts. But in the meantime we can’t move to any further code below, like you can’t click on other stuff on the page. Say you click on the heart emoji to like a post before it comes back. You click that heart and you’re getting no response because that’s code that needs to run. Slow function blocks further code running, so what can we do?
What if we try to delay a function directly using setTimeout?
setTimeout is a built in function - its first argument is the function to delay followed by ms to delay by.
1
2
3
4
5
function sayHello() {
console.log("Hello");
}
setTimeout(saytHello, 1000);
console.log("Me first!");
How will this piece of code execute?
Wait for one second
Print “Hello”
Print “Me first!”
Javascript is not enough - We need new pieces (some of which aren’t Javascript at all)
Our core Javascript engine has three main parts:
- Thread of execution
- Memory/variable environment
- Call stack
We need to add some new components:
- Web Browser APIs/Node background APIs
- Promises
- Event loop, Callback/Task queue and micro task queue
1
2
3
4
5
function sayHello() {
console.log("Hello");
}
setTimeout(sayHello, 1000);
console.log("Me first!")
Now Javascript is running in the browser because it has so much more than just Javascript in it. There’s still some other things that Javascript can’t do, including sockets, network requests, and console, among others. The above code will execute in the following order:
Define function sayHello
On the second line we’re sending a command/message to the browser because setTimeout is a label for a time in the browser. On this line it’s not going to do anything in Javascript.
Now we go to the next line and print “Me first” in the console while in the background time is passing.
Finally, when it’s time to execute sayHello and put “Hello” on the console
ES5 Web Browser APIs with callback functions
Problems
Our response data is only accessible in the callback function (callback hell). If you pass a function to setTimeout that is doing background work (getting posts maybe) you could use the returned data inside that function only because we don’t get to run that function and can’t store it’s return value anywhere. This causes callback hell because we do all tasks in one function which is also inside another function.
Maybe it feels a little odd to consider passing a function into another function just for it to run much later.
Once we started a background timer in the browser using setTimeout we had no way of tracking that back in Javascript.
Special objects are built into Javascript that get returned immediately when we make a call to an Internet browser API/feature (e.g. fetch) that’s setup to return promises (not all are).
Promises act as a placeholder for the data we expect to get back from the Internet browser feature’s background work.
Using two-pronged facade functions that both:
Start background web browser work
Return a placeholder object (promise) immediately in Javascript
Keep track of background features happening in the browser back into Javascript
1
2
3
4
5
6
7
function displayData(data) {
console.log(data)
}
const incomingData = fetch('https://instagram.com/blessingartcreator/posts/1')
incomingData.then(displayData);
console.log("Me first!");
Any code we want to run on the returned data must also be saved on the promise
object added using the .then method to the hidden property ‘onFulfilment’. Promise objects will automatically trigger the attached function to run (with its input being the returned data).
Let’s understand how our promise-deferred functionality gets back into Javascript to be run:
1
2
3
4
5
6
7
function displayData(data) {
console.log(data)
}
const incomingData = fetch('https://instagram.com/blessingartcreator/posts/1')
incomingData.then(displayData);
console.log("Me first!");
We’re defining a function displayPosts
Declaring a constant incomingData and telling Javascript that the fetch call on the right hand side which triggers some work in the background must also do something in Javascript because the result of it at that moment is stored on the left hand side. So incomingData will be the result in Javascript which is the consequence of fetch. Remember that fetch will do some work for us, but it will also automatically do some background work in the Internet browser.
By the time fetch is calling some data on that url we passed in, it has an empty array property called onFulfilled which is hidden but super important. Now our incomingData has a value property that stores the response that we’ll get from the Internet, and once we get it it’ll automatically run the function stored in the onFulfilled array. We add that function using .then method
Print “Me first!” on the console
We have a set of rules for executing asynchronously delayed code in Javascript:
Hold promise-deferred functions in a microtask queue and callback function in a task queue (callback queue) when the Web Browser Feature (API) finishes. Add the function to the call stack (i.e. run the function) when:
All global code has finished executing (the built in Javascript Event Loop checks this condition for you)
Prioritize functions in the microtask queue over the callback queue. Any browser API is added in the microtask queue.
Promises, Web APIs, the Callback & Microtask Queues and Event loop enable:
Non-blocking applications: This means we don’t have to wait in the single thread and don’t block further code from running.
However long it takes: We cannot predict when our browser feature’s work will finish so we let JS handle automatically running the function on its completion.
Web applications: Asynchronous JavaScript is the backbone of the modern web - letting us build fast ‘non-blocking’ applications.
There we have it. If you made it here thank you for reading! I hope this post will help you get started with writing pro-level JavaScript codes.
👋Let’s be friends! Follow me on Twitter and Instagram for more related content. Don’t forget to follow me also on Dev as well to get updated for new content.
Cheers!
CHECK OUT TOPCODER JAVASCRIPT FREELANCE GIGS