What About Coding
By himanshu123JavaScript

Callback in javascript

๐Ÿ‘‰ A function that is passed as an argument to another function.

Note: The function is then executed sometimes later inside the body. Everything in JavaScript is an object, and we can pass objects inside a function as parameters.

Why do we need callback

โ™ฆ๏ธ JavaScript runs code sequentially in top-down order. However, there are some cases where code runs (or must run) after something else happens and not sequentially. This is called asynchronous programming.

โ™ฆ๏ธ Callbacks help you control the order of execution. It makes sure that a function is not going to run before a task is completed, but will run right after the task has completed. It helps us develop asynchronous JavaScript code and keeps us safe from problems and errors.

How to create a callback function

1. Creating a callback as a function expression

JavaScript

function greetUser(name, callback) {
  console.log("Hi " + name);
  callback();
}

function sayBye() {
  console.log("Bye!");
}

greetUser("Rahul", sayBye);

2. Creating as Anonymous Function (with setTimeout)

JavaScript

setTimeout(function() {
  console.log("This message is shown after 3 seconds");
}, 3000);

3. Callback as an Arrow Function

JavaScript

setTimeout(() => {
  console.log("This message is shown after 3 seconds");
}, 3000);

All 3 will do the same job.

Callback helps in asynchronous programming

๐Ÿ‘‰ Callback helps in executing code only after a following response.

Here, the function will wait for 3 seconds to print, allowing us to control execution according to our need:

JavaScript

const printMeAfter3sec = () => {
  console.log("hello");
}

setTimeout(printMeAfter3sec, 3000);

Let's understand how callback works

JavaScript

console.log("Loading...")

function fetchDataCallback(callback) {
  console.log("Fetching data...")
  
  setTimeout(() => {
    const data = { id: 1, name: "John Doe" } // Simulating fetched data
    callback(data) // Call the callback with the data after fetching
  }, 2000) // Simulate a 2-second delay
}

// Using the function with a callback
fetchDataCallback((result) => {
  console.log("Data fetched:", result)
})

console.log("End...")

Explanation Step-by-Step

1. Global Execution Context

When JavaScript starts executing this code, it first creates a Global Execution Context. This includes all the variables, function declarations, and any code that needs to run at the top level. The function fetchDataCallback is defined and stored in memory (it isn't executed yet).

2. Function Invocation (Synchronous Part)

JavaScript

fetchDataCallback((result) => {
  console.log('Data fetched:', result);
});

fetchDataCallback is invoked with a callback function passed as an argument. A new execution context is created for this function call, where the callback parameter is assigned the anonymous function (result) => { console.log('Data fetched:', result); }.

3. Execution of console.log('Fetching data...')

Inside fetchDataCallback, the first statement is:

JavaScript

console.log('Fetching data...');

This is a synchronous operation, so it immediately logs "Fetching data..." to the console.

4. Encountering setTimeout

Next, we encounter:

JavaScript

setTimeout(() => {
  const data = { id: 1, name: 'John Doe' };
  callback(data);
}, 2000);

setTimeout is a Web API provided by the browser (or Node.js). It takes two arguments:

  1. A callback function to execute after the delay.
  2. A delay in milliseconds (2000ms or 2 seconds).

When JavaScript encounters setTimeout, it offloads this task to the Web API environment.

Important: The Web API handles the timer in the background while the main JavaScript thread continues executing other code.

5. Finishing the Synchronous Code

After setTimeout is sent to the Web API, the synchronous code inside fetchDataCallback finishes execution. At this point, the JavaScript call stack is empty.

6. Timer and Web APIs (Asynchronous Part)

While the main thread is free, the Web API timer runs in the background for 2 seconds (2000ms). After 2 seconds, the timer expires, and the callback function inside setTimeout is sent to the task queue.

7. Event Loop

The event loop continuously monitors the call stack and the task queue. Once the call stack is empty, the event loop pushes tasks from the task queue to the call stack.

8. Callback Execution (Async Code)

After 2 seconds, the event loop pushes the callback function inside setTimeout to the call stack:

JavaScript

() => {
  const data = { id: 1, name: 'John Doe' };
  callback(data);
}

Inside this function:

  • The data object is created: { id: 1, name: 'John Doe' }.
  • The callback function is invoked with this data object as an argument.

9. Executing the Passed Callback

Now, (result) => { console.log('Data fetched:', result); } is called with data as result.

A new execution context is created for this callback, logging:

Data fetched: { id: 1, name: 'John Doe' }.

Visual Timeline

  1. Start: Function fetchDataCallback is called โ†’ logs "Fetching data...".
  2. setTimeout: Offloaded to Web APIs โ†’ 2-second timer starts.
  3. Synchronous Code Ends: Main thread becomes idle โ†’ waits for async tasks.
  4. After 2 Seconds: Timer expires โ†’ callback is pushed to task queue โ†’ event loop moves it to the call stack.
  5. Callback Executes: Logs the final data.

Summary of Web APIs Role

The Web API (setTimeout) is responsible for the timer. The event loop ensures that after the timer expires, the callback is executed asynchronously once the call stack is clear. Callbacks allow us to handle asynchronous operations where some tasks (like waiting for data) are handled outside of the JavaScript execution engine.

Problem Statement: Fix with Callback

In the code below, the output of message returns undefined:

JavaScript

console.log("hello");

function asyncMe(name) {
  setTimeout(function () {
    return `${name}`;
  }, 1000);
}

const message = asyncMe("himanshu");
console.log(message);
console.log("bye");

Reason: The message output is undefined because asyncMe() is an asynchronous function, meaning the timer callback executes later, after the synchronous return has already resolved to undefined.

Solution:

Define a function inside a parameter (i.e., callback) and pass it inside setTimeout():

JavaScript

console.log("hello");

function asyncMe(name, callback) {
  setTimeout(function () {
    const message = `print to ${name}`;
    callback(message); // Call the callback function with the message
  }, 1000);
}

asyncMe("himanshu", function(message) {
  console.log(message); // Executed after the timeout
});

console.log("bye");

Question: Fix this with callback

Problem Code:

JavaScript

function getUserData(id) {
  setTimeout(() => {
    if (!id) {
      throw new Error("User ID is required!");
    }
    return { id, name: "John Doe" }; // This return won't work properly in async code
  }, 1000);
}

const user = getUserData(1);
console.log(user); // This will be undefined

Solution:

JavaScript

function getUserData(id, callback) {
  setTimeout(() => {
    if (!id) {
      return callback(new Error("User ID is required!"), null);
    }
    const user = { id, name: "John Doe" };
    callback(null, user);
  }, 1000);
}

// Using the function with a callback
getUserData(1, (error, user) => {
  if (error) {
    console.error("Error:", error.message);
  } else {
    console.log("User Data:", user);
  }
});

// Testing the error case
getUserData(null, (error, user) => {
  if (error) {
    console.error("Error:", error.message);
  } else {
    console.log("User Data:", user);
  }
});

Other uses of callback

1. Callback helps in calling an event

HTML

<!-- HTML -->
<button id="callback-btn">Click here</button>

JavaScript

// JavaScript
document.querySelector("#callback-btn")
  .addEventListener("click", function() {
    console.log("User has clicked on the button!");
  });

The first parameter is its type ("click"), and the second parameter is a callback function which logs the message when the button is clicked.

2. Callback function helps in creating a polyfill

For example, creating a polyfill for forEach:

JavaScript

let arr = [2, 4, 5, 3, 5];

const myForEach = (arr, cb) => {
  for (let i = 0; i < arr.length; i++) {
    const element = arr[i];
    cb(element);
  }
};

myForEach(arr, (name) => console.log(name));

๐Ÿ  Homework: Try creating polyfills for map, filter, and reduce.

Share

More recent posts

View all