Привет, так как 5 лет любят шоколад, я буду использовать это в качестве примера!

Предположим, вы пятилетний ребенок, и я даю вам шоколадку. Если вы откроете обертку и сразу же бросите шоколад в рот, это нормальная функция. С другой стороны, если вы дадите его своей маме, и она, скажем, через 5 минут развернет шоколад и бросит его вам в рот, вуаля у вас есть функция обратного вызова.

В программировании, если все данные, требуемые функцией, готовы, мы можем сразу вызвать эту функцию нами, как показано ниже:

function greet(name) {
    console.log("Hi, " + name )
}
greet("Brij")

С другой стороны, предположим, что мы должны получить имя из базы данных по сети, что займет некоторое время — так называемая асинхронная операция, и в таком случае если мы сами вызовем функцию приветствия(), имя по-прежнему будет undefined, и наш код не будет работать должным образом. В таких случаях мы передаем саму функцию приветствия() в качестве аргумента другой функции, скажем, fetchName(), которая сначала выполнит выборку, а затем, используя извлеченные данные, вызовет функцию приветствия(), как показано ниже:

function fetchName(cb) {
    const name = // code to get the name
    cb(name)
 }
fetchName(greet)

Обратите внимание, что здесь мы не вызываем функцию приветствия(); скорее это вызовет функция fetchName()! Другими словами, мы передаем функцию в качестве аргумента другой функции, которая позже вызовет эту функцию.

Я надеюсь, что вышесказанное опустит копейку дальше!