Javascript function for handling multiple unknown

2019-02-28 12:58发布

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条回答
forever°为你锁心
2楼-- · 2019-02-28 13:21

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);

查看更多
Summer. ? 凉城
3楼-- · 2019-02-28 13:35

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.

查看更多
叛逆
4楼-- · 2019-02-28 13:39

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
查看更多
登录 后发表回答