0

I came across the following code for a callback function. I understand the code, but I can't wrap my head around it. It just seems so counterintuitive.

function greet(name,callback1){
    callback1(name)
}
greet("John Cena", function (somerandomName) {
    console.log(somerandomName);
    return someRandomName;
}); // Output is John Cena and undefined. 

This is what I have understood from the code:

  1. We define a function greet, which takes 2 parameters name and callback1. Then we say that callback1's parameter is name. We don't return anything in the greet function, why?

  2. And when we invoke the greet function we pass the 2nd parameter as an Anonymous function whose parameter is someRandomName. Then we console.log(someRandomName). I added the return someRandomName, but this return did not work and I got the printed statement followed by undefined

Can someone please explain this in simple words, This just seems so counterintuitive.

pi2018
  • 347
  • 2
  • 11
  • 1
    The reasons why callbacks work like this is probably easier to grasp if `greet()` was actually doing something asynchronously. – Mark Jul 28 '18 at 01:33

4 Answers4

3

So I think it is important to understand that functions themselves can be parameters.

In this example you pass a string as the first parameter and then a function that takes that string as a parameter as the second parameter.

Functions do not always need to return something. Often a function might perform a manipulation on the dom, fetch data, configure something or alter pre existing variables. Of course you can return something if that is what is needed.

Adding return like you did does not do much. In order to actually return the name value you would have to write the original function like this.

function greet(name,callback1){
    return callback1(name)
}

Then you could do something like this

var wrestler = greet("John Cena", function (somerandomName) {
    console.log(somerandomName);
    return somerandomName;
});

console.log(wrestler) // prints John Cena

Its sort of an weird example since it serves no real purpose. Something like this might help you see whats going on.

    function greet(name, callback) {
      callback(name)
    }

    greet('John Cena', function(name){
      console.log('Hello ' + name) // prints Hello John Cena
    })

   OR return something and use it to manipulate dom

   function greet(name, callback) {
      return callback(name)
   }

   var greeting = greet('John Cena', function(name){
     return 'Hello ' + name
   })

   document.getElementById('message').innerHTML = greeting

   // somewhere in HTML...
   <h1 id='message'></h1>

Either way at least we are doing something to the first parameter now. And the things that you can do with callbacks are limitless.

Callbacks are a fundamental feature of javascript. They come into play alot when the first part of the function is asynchronous, such as a call to an api or database. In this case the first part would be the call to the database and the callback will not be fired until a value is obtained from the initial call. Recently, this callback pattern is being used less due to Promises, but callbacks are still useful.

So an example of a generic api call from a frontend to a backend. This would typically be done using the Fetch Api or with a library like Request or Axios. Just remember, the first function that is calling an endpoint takes some amount of time to execute and get data. The callback will not fire until that data is returned. Of course there would be a function on the backend that sends either an error or data back to the callback. I dont want to over complicate things, rather just give an idea of what callbacks are often used for.

function getDataFromBackend(endPoint, callback) {
  callback(error, data)
}

getDataFromBackend('/api/user', function(error, data) {
  if (error) {
    // handle error - show user error message in ui, etc
  }
   // do something with data - such as display welcome message to user, etc
})

I would suggest working with callbacks for practice. I find that when I use Node, or build an app with a frontend and backend I implement callbacks a lot for, since there is a lot of asynchronous communication happening. Hope I answered your questions.

benjaminadk
  • 468
  • 6
  • 18
1

First, you have return someRandomName but your parameter is called somerandomName. Variables are case sensitive; that's why your return value is different from what you want.

Your question is Why don't we return anything in the greet function. The answer is "I have no idea." You could return something. Some functions return things; some functions don't. That has nothing to do with the callback arrangement here.

function greet(name,callback1){
    return callback1(name)
}
var finalResult = greet("John Cena", function (someRandomName) {
    console.log(someRandomName);
    return someRandomName;
});

Now finalResult will be "John Cena".

If it helps at all, anywhere you're using an anonymous function, you could just as easily use a named function. (It's often uglier in practice, but for the sake of understanding the concepts...)

function greet(name,callback1){
    return callback1(name)
}
function myGreeterFunction(someRandomName) {
    console.log(someRandomName);
    return someRandomName;
});
var finalResult = greet("John Cena", myGreeterFunction);

Now it's perhaps easier to see that callback1(name) is the same thing as saying myGreeterFunction(name) in this case.

VoteyDisciple
  • 37,319
  • 5
  • 97
  • 97
  • Thanks I mistyped the word. I had the corrected spelling in my code but it stilled return undefined. But I think your suggestion of using named functions is much better than using anonymous functions. Thanks! – pi2018 Jul 28 '18 at 02:17
1

This is a pretty dated approach to JavaScript and you are totally right that it's counter intuitive. In this particular example there's no advantage of writing this with a callback and probably should be written as a promise,

greet("John Cena")
    .then(() => {
        return 'Next Action'
    })
    .then(nextAction => {
        console.log(nextAction)
    })

And as @mark-meyer pointed out this approach would be required if you had an asynchronous event.

Actually an AWS Lamda function actually has an optional callback defined as it's third argument. exports.myHandler = function(event, context, callback) {}. Which to be brutally honest I think is only there to cover cases where third party libraries aren't promised based.

Whilst passing a callback to a function is probably never the correct approach, there's still cases where passing a function to a function is valuable. Perhaps in a pub/sub system.

const events = {}

function subscribe (event, func) {
    if (events[event]) {
        events[event].push(func)
    } else {
        events[event] = [func]
    }
}

function publish (event) {
    if (events[events]) {
        events[event].forEach(func => func())
    }
}

And if you are writing with a fp approach link in RamdaJs chaining functions is going to be a huge part of what you write.

Does that clear things up a bit @pi2018?

stwilz
  • 2,226
  • 2
  • 21
  • 24
0

The general technique is called continuation-passing style – contrast with direct style.

The technique does allow for some interesting programs to emerge. Below, we have two ordinary functions add and mult. We write two programs using cont which allows us to string these functions together, where each step is the continuation of the one before it.

const cont = x => k =>
  cont (k (x))
  
const add = x => y =>
  x + y
  
const mult = x => y =>
  x * y
  
cont (1) (add (2)) (mult (3)) (console.log) // 9
//    1 ->
//        x => 2 + x
//        cont (3) ->
//                  x => 3 * x
//                  cont (9) ->
//                             x => console.log (x)
//                             cont (undefined)

We can sequence as many operations as needed –

const cont = x => k =>
  cont (k (x))
  
const add = x => y =>
  x + y
  
const mult = x => y =>
  x * y

cont (2) (mult (2)) (mult (2)) (mult (2)) (mult (2)) (console.log) // 32
//    2 -> 
//        x => 2 * x
//        cont (4) ->
//                   x => 2 * x
//                   cont (8) ->
//                              x => 2 * x
//                              cont (16) ->
//                                         x => 2 * x
//                                         cont (32) ->
//                                                    x => console.log (x)
//                                                    cont (undefined)

Continuation-passing style can be used to implement concurrent programs, but JavaScript provides a better native concurrency primitive now, Promise. There's even new async and await syntax to make working with Promises easier.

As wikipedia notes, continuation-passing style is used more by compilers and less by programmers. If you're trying to write concurrent programs, I would highly recommend you use Promises instead. Later versions of Node include util.promisify which allows users to convert a continuation-passing style function to a Promise-returning async function.

Mulan
  • 129,518
  • 31
  • 228
  • 259