Table of Contents
Callbacks in JavaScript:
Callbacks are simply Functions In JavaScript which are to be called and then executed after the execution of another function has finished. So how it happens? Actually, In JavaScript, functions are itself considered as objects and hence as all other objects, even functions can be sent as arguments to other functions. The most common and generic use case one can think of is setTimeout() function in JavaScript.
Consider the following Example of setTimeout() in JavaScript and hence try to get hands-on Callbacks JavaScript gave to us by default.
//with customary function signature setTimeout(function() { console.log('hello1'); }, 1000); //with arrow function signature setTimeout(() => { console.log('hello2'); }, 2000);
In the above example, I have used setTimeout() function by passing a callback function as an argument into it along with the second argument which is simply the no of milliseconds after which our callback function would get executed. I have shown two ways of passing a callback function here, one is the more of customary approach and the second one is with arrow function approach which is a bit modern way.
Hitting HTTP Requests in Javascript:
Suppose, I want to hit an HTTP Request to an API which fetches a random text for me. We won't be digging much into details of hitting HTTP Requests as this is out of the scope of this article.Now, to hit that API, you need to create two files:
index.html
<!DOCTYPE html> <html> <head></head> <body> <script src="app.js"></script> </body> </html>
app.js
const puzzleAPIhit = () => { const request = new XMLHttpRequest() request.addEventListener('readystatechange', (e) => { if (e.target.readyState === 4 && e.target.status === 200) { const data = JSON.parse(e.target.responseText); console.log(data.puzzle) } else if (e.target.readyState === 4) { console.log('An error has taken place') } }) request.open('GET', 'http://puzzle.mead.io/puzzle?wordCount=3') request.send() } puzzleAPIhit();
Now, when you open the "index.html" file in your browser, you can see a random string getting printed in the console.
Suggested Read: Redis vs MySQL Benchmarks
Callback Abstraction:
Now, what if we have a complex application or something like an entire game is built over this and hence the logic of generation of a random string is something which we should keep hidden or abstract from "users". To understand this, we can create three files:index.html
<!DOCTYPE html> <html> <body> <script src="makerequest.js"></script> <script src="app.js"></script> </body> </html>
makerequest.js
const puzzleAPIhit = () => { return 'some random string'; }
app.js
const myPuzzle = puzzleAPIhit(); console.log(myPuzzle);
Now, we need to replace the actual logic of finding the random string with the hardcoded return statement in puzzleAPIhit() function. But as the hitting of an HTTP request is asynchronous nature, we can't simply do this: (changing contents of makerequest.js and keeping rest two files intact)
makerequest.js
const puzzleAPIhit = () => { const request = new XMLHttpRequest() request.addEventListener('readystatechange', (e) => { if (e.target.readyState === 4 && e.target.status === 200) { const data = JSON.parse(e.target.responseText); console.log(data.puzzle) return data.puzzle; /* This is absolutely impossible the request.open() is asynchronous in nature. */ } else if (e.target.readyState === 4) { console.log('An error has taken place') } }) request.open('GET', 'http://puzzle.mead.io/puzzle?wordCount=3') request.send() }
Because in the console, it would be printing:
undefined Reliable Public Transportation //A random string
It is happening because, as the request.open() is asynchronous in nature, inside app.js, this is what happens:
- "puzzleAPIhit()" starts its execution but being asynchronous, it would move to the next statement while parallelly running the HTTP request.
- "console.log(myPuzzle);" gets executed even before the execution of "puzzleAPIhit()" is completed and hence prints undefined.
Solution? use callbacks. Here's what we can do:
app.js
const myPuzzle = puzzleAPIhit((error, puzzle) => { if(error) { console.log(`Error: ${error}`) } else { console.log(puzzle) } });
makerequest.js
const puzzleAPIhit = (callback) => { const request = new XMLHttpRequest() request.addEventListener('readystatechange', (e) => { if (e.target.readyState === 4 && e.target.status === 200) { const data = JSON.parse(e.target.responseText) callback(undefined, data.puzzle) } else if (e.target.readyState === 4) { callback('An error has taken place', undefined) } }) request.open('GET', 'http://puzzle.mead.io/puzzle?wordCount=3') request.send() }
index.html (no change)
What we have done? We have just replaced the synchronous call to asynchronous one by sending the callback in puzzleAPIhit() as an argument. And in the method puzzleAPIhit() itself, we called the callback after we have got our results which validates the basic definition of callbacks.
Top comments (1)
What if we do in this way. i.e initializing and sending request first
const puzzleAPIhit = (callback) => {
const request = new XMLHttpRequest()
request.open('GET', 'puzzle.mead.io/puzzle?wordCount=3')
request.send()
request.addEventListener('readystatechange', (e) => {
if (e.target.readyState === 4 && e.target.status === 200) {
const data = JSON.parse(e.target.responseText)
callback(undefined, data.puzzle)
} else if (e.target.readyState === 4) {
callback('An error has taken place', undefined)
}
})
}