Skillversitas

Callback Functions in JavaScript

A callback is a function passed as an argument to another function and called ("called back") later — either synchronously (immediately) or asynchronously (after an operation completes). Callbacks are a fundamental concept in JavaScript, forming the basis of event handling, array methods, Promises, and async/await.


What Is a Callback

// Functions as first-class citizens — can be passed around like variables

function greet(name) {
  console.log(`Hello, ${name}!`);
}

function execute(fn, argument) {
  fn(argument); // called here
}

execute(greet, "John"); // "Hello, John!"
// Callback — a function called after an operation completes

function completeTask(taskName, callback) {
  console.log(`Processing: ${taskName}...`);

  // Simulated work
  const done = true;

  if (done) {
    callback(null, taskName); // success
  } else {
    callback(new Error("Assignment failed"), null);
  }
}

// Usage
completeTask("Algorithms course", function(error, result) {
  if (error) {
    console.error(error.message);
  } else {
    console.log(`✅ Assignment done: ${result}`);
  }
});
// Processing: Algorithms course...
// ✅ Assignment done: Algorithms course

Code Example: Synchronous vs Asynchronous Callback

// === SYNCHRONOUS CALLBACK ===
// Called immediately, before the next line is executed

const courseList = ["Algorithms", "Database", "Web Dev", "Networking"];

// forEach, map, filter — synchronous callbacks
courseList.forEach(function(course, index) {
  console.log(`${index + 1}. ${course}`);
});
// Output appears immediately, line by line

// === ASYNCHRONOUS CALLBACK ===
// Called later, after the async operation completes

console.log("1. Start");

setTimeout(function() {
  console.log("2. This is async (appears later)");
}, 1000);

console.log("3. Done");

// Output:
// 1. Start
// 3. Done
// 2. This is async (appears later) ← after 1 second
// Callbacks with array methods

const student = [
  { name: "John", gpa: 3.75, credits: 13 },
  { name: "Amy", gpa: 3.90, credits: 14 },
  { name: "Mike", gpa: 2.80, credits: 12 },
  { name: "Anna", gpa: 3.50, credits: 15 },
];

// filter — synchronous callback
const cumLaude = student.filter(function(student) {
  return student.gpa >= 3.5;
});
console.log(cumLaude.map(m => m.name)); // ["John", "Amy", "Anna"]

// map — synchronous callback
const studentName = student.map(function(student) {
  return student.name;
});
console.log(studentName); // ["John", "Amy", "Mike", "Anna"]

// find — synchronous callback
const found = student.find(function(student) {
  return student.name === "Mike";
});
console.log(found); // { name: "Mike", gpa: 2.80, credits: 12 }

// reduce — synchronous callback
const totalCredits = student.reduce(function(acc, student) {
  return acc + student.credits;
}, 0);
console.log(`Total credits: ${totalCredits}`); // 54

Callback Hell

When callbacks are nested too deeply, the code becomes hard to read and maintain — this is called callback hell.

// ❌ CALLBACK HELL — multi-level nested callbacks

function login(username, callback) {
  setTimeout(() => {
    console.log("1. Login successful");
    callback({ token: "abc123" });
  }, 500);
}

function getProfile(token, callback) {
  setTimeout(() => {
    console.log("2. Profile fetched");
    callback({ name: "John", studentId: "2023001" });
  }, 500);
}

function getGrade(studentId, callback) {
  setTimeout(() => {
    console.log("3. Grade fetched");
    callback([{ course: "Algorithms", grade: 85 }]);
  }, 500);
}

function display(grade, callback) {
  setTimeout(() => {
    console.log("4. Grade displayed");
    callback(grade);
  }, 500);
}

// ❌ Callback hell — hard to read!
login("john", function(result1) {
  getProfile(result1.token, function(profile) {
    getGrade(profile.studentId, function(grade) {
      display(grade, function(final) {
        console.log("Done:", final);
      });
    });
  });
});

// ✅ SOLUTION: Promises or async/await (next chapter)

Code Explanation

Type Explanation Example
Sync callback Executed immediately array.map(), array.forEach()
Async callback Executed later setTimeout(), fetch(), fs.readFile()
Callback hell Callbacks nested too deeply Nested callbacks 3+ levels deep
Error-first callback (error, result) => {} Node.js convention

Error-first callback convention:
- First parameter: the error (or null if successful)
- Second parameter: the operation's result


Real-World Examples

1. Callback After Form Submit

// Simulated form submit with sequential validation

function submitForm(formData, callback) {
  console.log("Validating data...");

  // Validate name
  if (!formData.name || formData.name.length < 3) {
    return callback(new Error("Name at least 3 characters"), null);
  }

  // Validate email
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!emailRegex.test(formData.email)) {
    return callback(new Error("Invalid email"), null);
  }

  // Validate StudentID
  if (!/^\d{8}$/.test(formData.studentId)) {
    return callback(new Error("StudentID must be 8 digits"), null);
  }

  // Simulate sending to the server
  setTimeout(() => {
    callback(null, {
      status: "success",
      message: "Registration successful!",
      data: formData
    });
  }, 1000);
}

// Usage
submitForm({
  name: "John Smith",
  email: "[email protected]",
  studentId: "2023001"
}, function(error, result) {
  if (error) {
    console.error("❌ Error:", error.message);
  } else {
    console.log("✅", result.message);
    console.log("Data:", result.data);
  }
});

2. Loading Lecturer Data with Sequential Callbacks

// Simulated sequential data loading

function fetchFaculty(callback) {
  setTimeout(() => {
    const lecturer = [
      { id: 1, name: "Dr. Aaron", course: ["Algorithms", "Programming"] },
      { id: 2, name: "Dr. Sarah", course: ["Database", "Data Mining"] },
    ];
    callback(null, lecturer);
  }, 300);
}

function fetchSchedule(lecturerId, callback) {
  setTimeout(() => {
    const schedule = {
      1: [{ day: "Monday", time: "08:00" }, { day: "Wednesday", time: "10:00" }],
      2: [{ day: "Tuesday", time: "13:00" }, { day: "Thursday", time: "15:00" }],
    };
    callback(null, schedule[lecturerId] || []);
  }, 300);
}

function fetchRoom(lecturerId, callback) {
  setTimeout(() => {
    const room = { 1: "Lab 301", 2: "Room 205" };
    callback(null, room[lecturerId] || "Not determined yet");
  }, 300);
}

// Sequential loading — one by one
fetchFaculty(function(error, lecturerList) {
  if (error) return console.error(error);

  console.log(`📋 Found ${lecturerList.length} lecturers`);

  let loaded = 0;

  lecturerList.forEach(function(lecturer) {
    fetchSchedule(lecturer.id, function(error, schedule) {
      if (error) return console.error(error);

      fetchRoom(lecturer.id, function(error, room) {
        if (error) return console.error(error);

        console.log(`\n👤 ${lecturer.name}`);
        console.log(`   Mata Lecture: ${lecturer.course.join(", ")}`);
        console.log(`   Room: ${room}`);
        schedule.forEach(function(j) {
          console.log(`   📅 ${j.day} ${j.time}`);
        });

        loaded++;
        if (loaded === lecturerList.length) {
          console.log("\n✅ All lecturer data loaded successfully!");
        }
      });
    });
  });
});

3. Callbacks for Sorting and Filtering Data

const courseList = [
  { name: "Algorithms", credits: 3, semester: 2, price: 200000 },
  { name: "Web Dev", credits: 4, semester: 3, price: 250000 },
  { name: "Database", credits: 3, semester: 2, price: 200000 },
  { name: "Artificial Intelligence", credits: 3, semester: 5, price: 300000 },
  { name: "Networking", credits: 4, semester: 4, price: 250000 },
];

// Custom filter function with a callback
function filterBy(arr, callback) {
  const result = [];
  for (const item of arr) {
    if (callback(item)) {
      result.push(item);
    }
  }
  return result;
}

// Custom sort function with a callback
function sortBy(arr, callback) {
  return [...arr].sort(callback);
}

// Usage
const course3Credits = filterBy(courseList, m => m.credits === 3);
console.log("Course 3 credits:", course3Credits.map(m => m.name));

const cheapest = sortBy(courseList, (a, b) => a.price - b.price);
console.log("Cheapest:", cheapest[0].name, `$ ${cheapest[0].price}`);

const mostExpensive = sortBy(courseList, (a, b) => b.price - a.price);
console.log("Most expensive:", mostExpensive[0].name, `$ ${mostExpensive[0].price}`);

// Chaining
const courseExpensive3Credits = filterBy(
  filterBy(courseList, m => m.credits === 3),
  m => m.price > 200000
);
console.log("Course 3 credits mostExpensive:", courseExpensive3Credits.map(m => m.name));

Conclusion

  • A callback is a function passed as an argument to another function.
  • Synchronous callbacks are called immediately: map(), filter(), forEach().
  • Asynchronous callbacks are called later: setTimeout(), fetch(), fs.readFile().
  • The error-first callback is a convention: (error, result) => {}.
  • Callback hell occurs when callbacks are nested too deeply — the solution is Promises and async/await.
  • Callbacks are still widely used in array methods, event handlers, and legacy APIs.