1

I have a scenario where I would like to send in 2 or more functions (as parameters) into a handler function, and have that handler function execute each passed function as a callback function for the preceding function.

Here is a general concept of the function I am trying to write:

function functionChain() {
   // MAKE SURE WE HAVE AT LEAST 1 PARAMETER
   if ( arguments.length < 1 ) { return; }

   // for each parameter, call it (as a function)
   for ( var i=0; i<arguments.length; i++) {
     if ( typeof arguments[i] === 'function' ) {    
       call arguments[i];
     }
   }
}
// example
functionChain( function1, function2, function3 );

... so in the code above, each function will be called in succession.

Where I am getting stuck is how to treat each call as a callback when the previous function completes.

The way I would approach this is to have a variable (for simplicity, lets just say a global variable named functionChainComplete), and wait to launch the next function -- and of course, each function I call would set functionChainComplete to true. So, something like this:

// set global var for tracking
var functionChainComplete;

function functionChain() {
   // MAKE SURE WE HAVE AT LEAST 1 PARAMETER
   if ( arguments.length < 1 ) { return; }

   // SET GLOBAL VAR TO FALSE
   functionChainComplete = true;

   // for each parameter, call it (as a function)
   for ( var i=0; i<arguments.length; i++) {
     if ( typeof arguments[i] === 'function' ) {    
       if ( functionChainComplete == true ) {
         // call the next function and wait for true again
         functionChainComplete = false;
         call arguments[i];
       } else {
         // try again in 50 ms (maybe setTimeout)?
       }
     }
   }
}

function1() { 
    // do something, and when done, reset functionChainComplete
    functionChainComplete = true;
}

function2() { 
    // do something, and when done, reset functionChainComplete
    functionChainComplete = true;
}

function3() { 
    // do something, and when done, reset functionChainComplete
    functionChainComplete = true;
}

// example
functionChain( function1, function2, function3 );

As you can see, the code above does not address the callback piece, and I am not sure where to take it from here - I suspect some sort of recursive function? I am stuck.

3
  • 1
    Do you care about the execution order of the functions when an event triggers? What if some are async? Perhaps a use case would explain what you need vs what you are asking for :) Commented May 29, 2017 at 21:35
  • 1
    "...have that handler function execute each passed function as a callback function for the preceding function..." It's not entirely clear what you mean by that. So if I do functionChain(a, b, c, d), you want (in effect) a(function() { b(function() { c(function() { d(); } } })? What about arguments, should they be passed on? Commented May 29, 2017 at 21:35
  • order is paramount, and no need to pass params, although that would be useful (but I was thinking that was just another headache to solve later). thanks. In example, function 1 should run and complete, then function2, then 3. each function runs async, which is why i need this functionChain in the first place -- trying to solve a scenario that occurs in my app a lot - a series of functions that need to be called one after the other - figured a single function i can pass functions into would make it more streamlined. Thanks - Commented May 29, 2017 at 21:41

3 Answers 3

1

Say you have some function, double, that takes an argument, x, and a callback, k

const double = (x, k) =>
  k(x * 2)
  
double(2, console.log) // 4
double(3, console.log) // 6

Now say we want to run it 3 times in a row

const double = (x, k) =>
  k(x * 2)
      
const tripleDouble = (x, k) =>
  double(x, y =>
    double(y, z =>
      double(z, k)))
      
tripleDouble(2, console.log) // 16
tripleDouble(3, console.log) // 24

But of course we had to statically code each continuation (y => ..., and z => ...). How would we make this work with a variable amount (array) of functions?

const double = (x, k) =>
  k(x * 2)
  
const composek = (...fs) => (x, k) =>
  fs.reduce((acc, f) =>
    k => acc(x => f(x, k)), k => k(x)) (k)
  
const foo = composek(double, double, double)

foo(2, console.log) // 16
foo(3, console.log) // 24

This is ripe for some abstraction tho, and introduces my favourite monad, the Continuation Monad.

const Cont = f => ({
  runCont: f,
  chain: g =>
    Cont(k => f(x => g(x).runCont(k)))
})

Cont.of = x => Cont(k => k(x))

const composek = (...fs) => (x, k) =>
  fs.reduce((acc,f) =>
    acc.chain(x =>
      Cont(k => f(x,k))), Cont.of(x)).runCont(k)
      
const double = (x, k) =>
  k(x * 2)
  
const foo = composek(double, double, double)

foo(2, console.log) // 16
foo(3, console.log) // 24

If you have freedom to change the functions you're chaining, this cleans up a little bit more – here, double has 1 parameter and returns a Cont instead of taking a callback as a second argument

const Cont = f => ({
  runCont: f,
  chain: g =>
    Cont(k => f(x => g(x).runCont(k)))
})

Cont.of = x => Cont(k => k(x))

// simplified
const composek = (...fs) => (x, k) =>
  fs.reduce((acc,f) => acc.chain(f), Cont.of(x)).runCont(k)

// simplified
const double = x =>
  Cont.of(x * 2)
  
const foo = composek(double, double, double)

foo(2, console.log) // 16
foo(3, console.log) // 24

Of course if double was actually asynchronous, it would work the same

// change double to be async; output stays the same
const double = x =>
  Cont(k => setTimeout(k, 1000, x * 2))

const foo = composek(double, double, double)

foo(2, console.log) // 16
foo(3, console.log) // 24
Sign up to request clarification or add additional context in comments.

1 Comment

This takes care of your problem of threading data through the functions too. Before you just had a(), b(), c(), but with this setup, a can pass data to b and so on. This is one of my favourite topics of programming, but I have to get going for a class. I'll be back later tonight if you have other questions ^_^
0

Something like this? (See comments, but fairly self-explanatory.)

function functionChain() {
    var args = arguments;

    // MAKE SURE WE HAVE AT LEAST 1 PARAMETER
    if ( args.length < 1 ) { return; }

    // Start the process
    var i = -1;
    go();

    function go() {
        // Pre-increment so we start at 0
        ++i;
        if (i < args.length) {
            // We have a next function, do it and continue when we get the callback
            args[i](go);
        }
    }
}

Example:

function functionChain() {
    var args = arguments;
    
    // MAKE SURE WE HAVE AT LEAST 1 PARAMETER
    if ( args.length < 1 ) { return; }

    // Start the process
    var i = -1;
    go();

    function go() {
        // Pre-increment so we start at 0
        ++i;
        if (i < args.length) {
            // We have a next function, do it and continue when we get the callback
            args[i](go);
        }
    }
}

// Just some functions for an example:
function a(callback) {
  console.log("a");
  callback();
}

function b(callback) {
  console.log("b");
  callback();
}

// Note this one is async
function c(callback) {
  setTimeout(function() {
    console.log("c");
    callback();
  }, 100);
}

function d(callback) {
  console.log("d");
  callback();
}

functionChain(a, b, c, d);


That said, one of the reasons for promises is to allow composing possibly-async functions. If your functions returned promises, we'd use the reduce idiom:

function functionChain() {
    // Assumes the functions return promises (or at least thenables)
    Array.prototype.reduce.call(arguments, function(p, f) {
      return p.then(f);
    }, Promise.resolve());
}

function functionChain() {
    Array.prototype.reduce.call(arguments, function(p, f) {
      return p.then(f);
    }, Promise.resolve());
}

// Just some functions for an example:
function a(callback) {
  return new Promise(function(resolve) {
    console.log("a");
    resolve();
  });
}

function b(callback) {
  return new Promise(function(resolve) {
    console.log("b");
    resolve();
  });
}

// Note this one has a delay
function c(callback) {
  return new Promise(function(resolve) {
    setTimeout(function() {
      console.log("c");
      resolve();
    }, 100);
  });
}

function d(callback) {
  return new Promise(function(resolve) {
    console.log("d");
    resolve();
  });
}

functionChain(a, b, c, d);

Comments

0

This could be done with nsynjs:

  1. Wrap all slow functions with callbacks into nsynjs-aware wrappers (see wait()),
  2. Put your logic into function as if it was synchronous (see synchronousCode()),
  3. Run that function via nsynjs engine (see nsynjs.run())

<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>
<script>
		var wait = function (ctx, ms) {
			setTimeout(function () {
				console.log('firing timeout');
				ctx.resume();
			}, ms);
		};
		wait.nsynjsHasCallback = true;

		function synchronousCode() {
			function function1() { 
				console.log('in function1');
				wait(nsynjsCtx,1000);
			};

			function function2() { 
				console.log('in function2');
				wait(nsynjsCtx,1000);
			};

			function function3() { 
				console.log('in function3');
				wait(nsynjsCtx,1000);
			};
			
			
			function functionChain() {
			   // MAKE SURE WE HAVE AT LEAST 1 PARAMETER
			   if ( arguments.length < 1 ) return;

			   for ( var i=0; i<arguments.length; i++) {
				 //console.log(i,arguments[i]);
				 if ( typeof arguments[i] === 'function' ) {    
					 arguments[i]();
				 };
			   };
			};
			
			functionChain(function1,function2,function3);
		}
		
		nsynjs.run(synchronousCode,{},function(){
			console.log("Synchronous Code done");
		})
	</script>

See https://github.com/amaksr/nsynjs/tree/master/examples for more examples.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.