28

I'm getting around to learning JavaScript - really learning JavaScript. I come from a PHP background so some JavaScript concepts are still new to me, especially asynchronous programming. This question might have already been answered many times before but I have not been able to find an answer. It might be because I don't really even know how to ask the question other than by showing an example. So here it is:

When using the deferred package from npm, I see the following example:

delayedAdd(2, 3)(function (result) {
  return result * result
})(function (result) {
  console.log(result); // 25 
});

They refer to this as chaining and it actually works as I'm currently using this code to check when a promise is resolved or is rejected. Even though they call it chaining, it reminds me of trailing closures like in Swift.

I don't really understand what type of chaining this is since we have a function invocation and then immediately after, an anonymous function enclosed in parentheses.

So I guess I have two questions.

  1. What pattern is this?
  2. How does it work? This may be a loaded question but I like to know how something works so when someone asks me about this I can give them a detailed explanation.

Here is the delayedAdd function:

var delayedAdd = delay(function (a, b) {
  return a + b;
}, 100);

which uses the following function:

var delay = function (fn, timeout) {
  return function () {
    var def = deferred(), self = this, args = arguments;

    setTimeout(function () {
      var value;
      try {
        value = fn.apply(self, args));
      } catch (e) {
        def.reject(e);
        return;
      }
      def.resolve(value);
    }, timeout);

    return def.promise;
  };
};
200_success
  • 7,286
  • 1
  • 43
  • 74
Andrew Schools
  • 321
  • 3
  • 10
  • It would help a lot if you could add the code of `delayedAdd`. – Bergi Jun 02 '15 at 15:17
  • 1
    @Bergi Sorry, I forgot to add that function. I have updated my question. – Andrew Schools Jun 02 '15 at 15:40
  • Thanks. It is indeed odd (though interesting) that promises are functions, not just plain objects with `then` methods. – Bergi Jun 02 '15 at 15:53
  • 1
    The most important part of those docs reads: "*`promise.then(onsuccess, onfail);` [standard pattern] In 'deferred', `promise` is really a `then` function, so you may use promise function directly: `promise === promise.then; // true` [and that allows the following syntax] `promise(onsuccess, onfail);` [to be equivalent to a then call]*" – Bergi Jun 02 '15 at 19:47
  • @Bergi - And when that promise function is invoked it returns another function which allows me to add more links to the chain. Am I understanding this correctly? – Andrew Schools Jun 02 '15 at 21:24
  • 1
    Exactly - in promise terms, the `.then` method returns another promise. – Bergi Jun 02 '15 at 21:27
  • Mmmm, thenables without then! Very odd. – Roamer-1888 Jun 02 '15 at 21:28

6 Answers6

22

It's actually really easy to understand. Let's look at what's going on here when the expression is evaluated:

First the delayedAdd(2, 3) function will be called. It does some stuff and then returns. The "magic" is all about its return value which is a function. To be more precise it's a function that expects at least one argument (I'll get back to that).

Now that we evaluated delayedAdd(2, 3) to a function we get to the next part of the code, which is the opening parenthesis. Opening and closing parenthesis are of course function calls. So we're going to call the function that delayedAdd(2, 3) just returned and we're going to pass it an argument, which is what gets defined next:

That argument is yet another function (as you can see in your example). This function also takes one argument (the result of the computation) and returns it multiplied by itself.

This function that was returned by the first call to delayedAdd(2, 3) returns yet another function, which we'll call again with an argument that is another function (the next part of the chain).

So to summarize we build up a chain of functions by passing our code to whatever function delayedAdd(2, 3) returned. These functions will return other functions that we can pass our functions again.

I hope this makes the way it works somewhat clear, if not feel free to ask more.

mhlz
  • 3,497
  • 2
  • 23
  • 35
14

mhlz's answer is very clear. As a supplementary, here I compose a delayedAdd for your to better understand the process

function delayedAdd(a, b) {
  var sum = a + b
  return function(f1) {
    var result1 = f1(sum)
    return function(f2) {
      f2(result1)
    }
  }
}

Where in your example code, the function you passed as f1 is:

function (result) {
  return result * result
}

and f2 is:

function (result) {
  console.log(result)
}
Leo
  • 13,428
  • 5
  • 43
  • 61
7

Functions are first-class citizens in JS - that means (among others), they can take the role of actual parameters and function return values. Your code fragment maps functions to functions.

The signatures of the functions in your chained call might look like this.

delayedAdd: number -> fn                     // returns function type a
         a: fn ( number -> number) -> fn     // returns function type b
         b: fn ( number -> void )  -> void   // returns nothing ( guessing, cannot know from your code portion )

General setting

Of course, JS is a weakly typed language, so the listed signatures are derived from the code fragment by guessing. There is no way to know whether the code actually does what is suggested above apart from inspecting the sources.

Given that this showed up in the context of 'chaining', the signatures probably rather look like this:

delayedAdd: number x number -> fn (( fn T -> void ) -> ( fn T -> void ))

Which means that delayedAdd maps two numbers to a function x, which maps functions of arbitrary signatures to functions of the same signature as itself.

So who would do anything like this ? And why ?

Imagine the following implementation of x:

 //
 // x
 // Collects functions of unspecified (possibly implicit) signatures for later execution.
 // Illustrative purpose only, do not use in production code.
 //
 // Assumes 
 function x ( fn ) {
     var fn_current;

     if (this.deferred === undefined) {
         this.deferred = [];
     }

     if (fn === undefined) {
         // apply functions
         while ( this.deferred.length > 0 ) {
             fn_current = this.deferred.shift();
             this.accumulator = fn_current(this.accumulator);
         }
         return this.accumulator;
     }

     else {
         this.deferred.push ( fn );
     }

     return this;
 }

Together with a function delayedAdd that actually returns an object of the following kind ...:

 function delayedAdd ( a1, a2) {
     return x ( function () { a1 + a2; } );
 }

... you'll effectively register a chain of functions to be executed at some later point of time (e.g. in a callback to some event).

Notes and reminders

  • JS functions are JS objects
  • The signatures of the registered functions may actually be arbitrary. Considering them to be unified just serves to keep this exposition simpler (well ...).

Caveat

I do not know whether the outlined codeis what node.js does (but it could be ... ;-))

collapsar
  • 17,010
  • 4
  • 35
  • 61
6

To be fair this pattern can be either chaining or currying(or partial application). Depending how it's implemented. Note this is a theoretical answer to provide more information about the pattern and not your specific use case.

Chaining

There is nothing special here because we can just return a function that will be called again. Functions in javascript are first class citizens

function delayedAdd(x, y) {
    // In here work with x and y
    return function(fn) {
        // In here work with x, y and fn
        return function(fn2) {
            //Continue returning functions so long as you want the chain to work
        }    
    }
}

This make it unreadable in my opinion. There is a better alternative.

function delayedAdd(x, y) {
    // In here work with x and y
    return {
        then: function(fn) {
        // In here work with x, y and fn
            return {
                then: function(fn2) {
                //Continue returning functions so long as you want the chain to work
                }
            }    
        }
    }
}

This changes the way your functions are called from

delayedAdd(..)(..)(..); // 25 

is transformed to

delayedAdd().then().then()

Not only is more readable when you are passing several callback functions but allows a distinction from the next pattern called currying.

Currying

The term cames after the mathematician Haskell Curry. The definition is this

In mathematics and computer science, currying is the technique of translating the evaluation of a function that takes multiple arguments (or a tuple of arguments) into evaluating a sequence of functions, each with a single argument (partial application). It was introduced by Moses Schönfinkel and later developed by Haskell Curry.

Basically what it does is take several arguments and merge with the subsecuents and apply them to the original function passed in the first argument.

This is a generic implementation of this function taken from Stefanv's Javascript Patterns.

{Edit}

I changed my previous version of the function to one which has partial application included to make a better example. In this version you must call the function with no argument to get the value returned or you will get another partially applied function as result. This is a very basic example, a more complete one can be found on this post.

function schonfinkelize(fn) {
    var slice = Array.prototype.slice,
    stored_args = [],
    partial = function () {
        if (arguments.length === 0){
            return fn.apply(null, stored_args);
        } else  {
            stored_args = stored_args.concat(slice.call(arguments));
            return partial;
        }
    };
    return partial;
}

This are the results of the application of this function

 function add(a, b, c, d, e) {
     return a + b + c + d + e;
 }
 schonfinkelize(add)(1, 2, 3)(5, 5)(); ==> 16

Note that add (or in your case delayedAdd) can be implemented as the curying function resulting in the pattern of your example giving you this

delayedAdd(..)(..)(..); // 16

Summary

You can not reach a conclusion about the pattern just by looking at the way the functions are called. Just because you can invoke one after the other it doens't mean is chaining. It could be another pattern. That depends on the implementation of the function.

Community
  • 1
  • 1
devconcept
  • 3,665
  • 1
  • 26
  • 40
  • That `schoenfinkelize` function is completely off. It doesn't curry (or schönfinkel), but is only partial application. A proper one would work for `schönfinkelize(add)(1, 2)(3)(5, 5)`. – Bergi Jun 02 '15 at 15:16
  • I like how to brought up currying in your example. This really helped along with the other answers located here. So playing around with this some more, can I assume this is currying if I did something like return an object that supported say add, subtract, multiply and division functions? – Andrew Schools Jun 02 '15 at 15:32
  • @AndrewSchools: No, not exactly. That would just be method chaining. – Bergi Jun 02 '15 at 15:58
  • @Bergi I updated my answer. Thanks. The previous function was an example pasted in here to show another way this pattern can be used but cannot be chained more than two times, that is one partial application. The new one can be called infinite times but remember the total number of arguments passed must be equal of the arguments expected by the target function. – devconcept Jun 02 '15 at 19:10
  • That's not a good idea, you cannot do `var add1 = schönfinkelize(add)(1); add1(2); add1(3)` any more (which should yield `3` and `4` respectively). Currying needs to be referentially transparent. – Bergi Jun 02 '15 at 19:18
  • 1
    @Bergi as far as I understand _"Currying and partial function application are often conflated. One of the significant differences between the two is that a call to a partially applied function returns the result right away, not another function down the currying chain"_ so if I understand correctly currying is about the number of arguments of the pased function that is make the evaluation of the function by steps. Mi original function takes 6 arguments. I should return functions as long as I don't have the required number. What you are asking is partial application of fn(x, y) { return x + y } – devconcept Jun 02 '15 at 19:44
  • 1
    My example was for an `add` function of arity 2. Sorry if that was confusing. For your `add` function from the answer that takes 5 arguments, a snippet that shows the violated referential transparency could be `var add_c = schönfinkelize(add); var addTo3 = add_c(1, 2), addTo6 = addTo3(3); alert(addTo6()(5, 5)); alert(addTo6(1, 1)); alert(addTo3(4,5)(6));` Try it! – Bergi Jun 02 '15 at 19:53
  • @Bergi I see. I understand better now. The last two alerts made your point when I tried to refactor the function. I didn't add a complete example to my answer because I think is too much for this case so I added a reference link on how is done. Thanks – devconcept Jun 02 '15 at 21:19
5

All excellent answers here, especially @mhlz and @Leo, I'd like to touch on the chaining part you've mentioned. Leo's example shows the idea of calling functions like foo()()() but only works for fixed number of callbacks. Here's an attempt to imlpement unlimited chaining:

delayedAdd = function da(a, b){
// a function was passed: call it on the result
if( typeof a == "function" ){
     this.result = a( this.result )
}
else {
     // the initial call with two numbers, no additional checks for clarity.
     this.result = a + b;   
}
// return this very function
return da;
};

Now you can chain any number of functions in () after the first call:

// define some functions:
var square = function( number ){ return number * number; }
var add10 = function( number ){ return number + 10; }
var times2 = function( number ){ return number * 2; }
var whatIs = function( number ){ console.log( number ); return number; }

// chain them all!
delayedAdd(2, 3)(square)(whatIs)(add10)(whatIs)(times2)(whatIs);
// logs 23, 35 and 70 in the console.

http://jsfiddle.net/rm9nkjt8/3/

pawel
  • 35,827
  • 7
  • 56
  • 53
2

If we expand this syntax logically we would reach something like this:

var func1 = delayedAdd(2, 3);
var func2 = function (result) {
    return result * result
};
var func3 = function (result) {
    console.log(result);
};

var res = func1(func2); // variable 'res' is of type 'function'
res(func3);
frogatto
  • 28,539
  • 11
  • 83
  • 129
  • This is **way** more readable. Cluttering code never pays off. – acdcjunior Jun 02 '15 at 14:40
  • @acdcjunior: this is way more boilerplate that begs to be omitted. – Bergi Jun 02 '15 at 15:58
  • @Bergi Important point: In real life we'd be giving those vars more meaningful names (not `funcN` and on). If that's not enough, well, this is not complicated: If you think having to reread a code three times to get it, and have people asking in so.com what your code does (instead of your code being self-explanatory) is OK, then I guess we agree to disagree. – acdcjunior Jun 02 '15 at 16:28
  • 1
    @acdcjunior: Well, people come to SO all the time to understand promises and what `.then` means. This is just a different notation for the same thing. Once you're accustomed to the patterns, you don't want to miss them. Self-documenting variable names are mostly used in the parameters of the anonymous callback functions - instead of `result`. – Bergi Jun 02 '15 at 16:31
  • @Bergi I agree with you, patterns should be followed. My problem is not with stacked functions or using promises. You see, I do get the OP's code, but I'd still use more variables, separate the functions. The pattern would still be in use, but this is about making it more readable. (There's no relation: one could use promises perfectly, but the code could still be an unreadable mess.) Anyway, that's why I said we should agree to disagree, I get what that code does, but I don't think it is as readable as it could be. You do. Guess we should say this is a matter of opinion and call it quits. – acdcjunior Jun 02 '15 at 18:41