问题
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.
回答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
回答2:
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);
回答3:
This could be done with nsynjs:
- Wrap all slow functions with callbacks into nsynjs-aware wrappers (see wait()),
- Put your logic into function as if it was synchronous (see synchronousCode()),
- 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.
来源:https://stackoverflow.com/questions/44250521/javascript-function-for-handling-multiple-unknown-callbacks