DEV Community

Full Stack Geek
Full Stack Geek

Posted on

Callbacks in JavaScript

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)

Collapse
 
sushant14320 profile image
sushant14320

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)

}

})

}