Search code examples
javascriptnode.jsfunctional-programmingreturn-by-reference

Javascript function loading (by reference?)


While I have many years experience programming (in numerous languages), my background is not Javascript. Added to that is the fact that the Javascript of today is not the Javascript I first played with many years ago. It's much more sophisticated and powerful. That said, I'm struggling to understand some function payload dynamics.

Function calls where the function actually returns something are intuitive, but Javascript seems to do something with functions that I can't get my head around. I could just copy/paste code, or I could try to work out how to reuse this pattern in my own code.

For example, the following Mongoose call does a find of all records in the User model, and somehow the result of the call ends up in the second argument of the passed function (by reference?).

User.find({}, function(err, users) {  // Get all the users
  if (err) throw err;

  console.log(users); // Show the JSON Object    
});

Here's another example using a simple forEach on an array. Somehow, the forEach populates the 'user' argument.

users.forEach(function(user) {
        console.log(user.username, ': Admin = ', (user.admin ? 'Yes' : 'No'));
});

Can anyone explain this, and/or point me to a good guide on how/why this works?

I've seen the same pattern across Node.js and it's a bit of a stumbling block.

Have I missed something obvious or is this simply a peculiarity around functional programming?

Jon


Solution

  • This is called Continuation Passing Style. It is sometimes used to encapsulate asynchronous behaviours, like the Mongoose example you provided, but other times it can be used in synchronous ways, like the .forEach example.

    To see how this works, it's easy if we make up our own forEach.

    function forEach(xs, f)
      for (var i=0, len=xs.length; i<len; i++) {
        f(x[i]);
      }
    }
    
    forEach([1,2,3], function(x) { console.log(x); })
    

    So the way this works should be pretty easy to see: We can see that xs is set to our array [1,2,3] where we do a regular for loop inside the function. Then we see f is called once per element inside the loop.

    The real power here is that functions are first-class members in JavaScript and this enables the use of higher-order functions. This means .forEach is considered a higher-order function because it accepts a function as an argument.

    As it turns out, forEach could be implemented a lot of different ways. here's another.

    forEach(xs, f) {
      if (xs.length > 0) {
        f(xs[0]);
        forEach(xs.slice(1), f);
      }
    }
    

    The idea here is you should get comfortable with sending functions around in JavaScript. You can even return functions as the result of applying another function.

    function add(x) {
      return function(y) {
        return x + y;
      }
    }
    
    function map(xs, f) {
      function loop(ys, xs) {
        if (xs.length === 0)
          return ys;
        else
          return loop(ys.concat(f(xs[0])), xs.slice(1));
      }
      return loop([], xs);
    }
    
    map([1,2,3], add(10)); //=> [11,12,13]
    

    Before long, you'll be be knee-deep in the functional paradigm and learning all sorts of other new things.

    Functions !