请您解释一下,如何在JavaScript中编写真正基本的流控制?谢谢。
flow([
function(callback) { /* do something */ callback(); /* run next function */ },
function(callback) { /* do something */ callback(); /* run next function */ },
function(callback) { /* do something */ callback(); /* run next function */ },
function(callback) { /* do something */ callback(); }
], function() {
alert("Done.");
});
答案 0 :(得分:7)
这样的事情会起作用吗?
function flow(fns, last) {
var f = last;
for (var i = fns.length - 1; i >= 0; i--)
f = makefunc(fns[i], f);
f();
}
function makefunc(f, g) {
return function() { f(g) }
}
答案 1 :(得分:1)
我在最近的一个项目中做了很多。我写了一些代码来帮助管理它。这是代码。您将bundledAsync函数传递给具有“calls”参数和“bundleCallback”参数的对象。 calls参数是表示要调用的函数的objets数组。在fn param中,存储对实际参数的引用。在“args”参数中,存储参数。传入的每个函数的最后一个参数必须是一个必须调用的回调函数。
我很难记录我的代码并使其对其他人有用,但这对我来说真的很有用。我怀疑其他人写了类似的东西,或许是有适当记录的。如果您找不到它,并需要帮助解决这个问题,请告诉我。
/**
This is a way to submit multiple async calls, and be notified when they've all finished
<pre>
NameSpace.bundledAsync({
calls:[
{
fn: service.getGroups,
args: [
function(listsArg){
listsSummary = listsArg;
}
],
calls: function(){return UNAB.Util.makeArray(listsSummary, function(list){
return {
fn: service.getGroup,
args: [list.id, function(resp){listsDetail.push(resp)}]
}
})}
}
],
bundleCallback: function(){
callback(listsDetail)
}
});
</pre>
@class bundledAsync
@static
*/
NameSpace.bundledAsync = function(options){
var callbacksLeft = 0;
var calls = $.grep(options.calls, function(call){return call});
if(options.hasOwnProperty("bundleCallback") && typeof options.bundleCallback != "function"){
throw new Error("bundleCallback, passed to bundledAsync, must be a function.");
}
if(options.chain){ // if this is true, sibling calls will run in succession, not in parallel
calls.reverse();
var newCalls = [calls.pop()];
var lastCall = newCalls[0];
while(calls.length > 0){
if(lastCall.calls){
throw new Error("You can't nest calls if you're in chain mode");
}
lastCall.calls = [calls.pop()];
lastCall = lastCall.calls[0];
}
calls = newCalls;
}
var decrimentCallbacksLeft = function(){
if(options.name){
// log.debug("Starting decrimentCallbacksLeft for: " + options.name + ". Decrimenting callbacksLeft to: " + (callbacksLeft - 1));
}
if(--callbacksLeft == 0 && options.bundleCallback){
// log.debug("No callbacks left. Calling bundleCallback for name: " + options.name);
options.bundleCallback();
}
}
var doCalls = function(callsToDo){
if(typeof callsToDo == "function"){
callsToDo = callsToDo();
}else{
callsToDo = $.extend(true, [], callsToDo);// in case we want to reuse the calls
}
// right away, return if the calls are empty
// check to make sure callbacksLeft == 0, because
// we may be dealing with nested calls
if(callsToDo.length ==0 && callbacksLeft == 0){
// log.debug("callsToDo is empty, so call the callback right away.");
options.bundleCallback();
return null;
}
callbacksLeft += callsToDo.length;
$.each(callsToDo, function(index, call){
var numFns = 0;
// // Look through the args searching for functions.
// // When one is found, wrap it with our own function.
// // This assumes that each function has exactly one
// // callback, and that each callback is called exactly once
// args can be a function which will return the args,
// that way, you don't have to determine the args for the function until the moment it's called
call.args = call.jitArgs? call.args():call.args;
$.each(call.args, function(index, arg){
if(typeof arg === "function"){
numFns++;
// Here's where we wrap the original function's callback
call.args[index] = function(){
// when we get to this point, we know that the original function has totally completed,
// and we can call any functions chained to this one, or finish the whole process
arg.apply(null, arguments); // call the original callback
if(call.calls){
// maybe we don't want to create the child calls until after
// the parent has returned. In that case, pass a function instead of an array
if(typeof call.calls === "function"){
call.calls = call.calls();
}
// if this call has any call of its own, send those out now
doCalls(call.calls);
}
decrimentCallbacksLeft();
}
}
});
if(numFns!=1){
throw new Error("Each function passed to bundledAsync must have one and only one arg which is a function");
}
// if(call.fn.length != call.args.length){
// log.warn("The current function is being called with a different number of arguments that that with which it was declared. Should be: "+call.fn.length+", was: "+call.args.length+" \n" + call.fn.toString());
// }
call.fn.apply(null, call.args);
});
}
doCalls(calls);
}
答案 2 :(得分:1)
我建议阅读continuation-passing style。看来你的目标是,给定一个带有延续参数的函数数组,将它们链接在一起,使得继续进行到数组中的下一个函数。
以下是这种功能的实现:
function flow(funcArr, funcDone) {
function proceed(i) {
if (i < funcArr.length) {
return function() {
funcArr[i](proceed(i+1));
}
} else {
return funcDone;
}
}
proceed(0)();
}
编辑:Anon.'s answer更短更简单。
以下是它的工作原理:proceed(i)
返回一个回调函数,调用i th 函数(或funcDone
,如果数组中没有任何函数)。因为proceed(i)
返回回调而不是回复,我们可以使用proceed(i+1)
作为连续函数。
样本用法:
flow([
function(cb) { print("Thing one"); cb(); },
function(cb) { print("Thing two"); cb(); },
function(cb) { print("Thing three"); cb(); },
function(cb) { print("Thing four"); cb(); },
], function() {
print("Done.");
});
现在尝试删除其中一个cb();
来电。它将打破链条,这可能正是你想要的。另一个很酷的事情是,您可以将cb
移到全局变量中,稍后再调用它来恢复流程。
请记住,这种方法有一个缺点:许多(如果不是全部)JavaScript解释器不优化尾递归。如果funcArr
太长,您可能会出现堆栈溢出。这是在JavaScript中使用延续传递样式的交叉。
答案 3 :(得分:0)
(function(){
function a(cb) { alert('hi'); cb(); }
function b(cb) { alert('there'); cb(); }
function c(cb) { alert('replace alert with console.log for ease'); cb(); }
var done = function() { alert('done'); }
a(b(c(done)));
})()
答案 4 :(得分:0)
// callback is a global function, I assume
function flow(funcArr, funcEnd) {
for (var i = 0; i < funcArr.length; i++) {
funcArr[i](callback);
}
funcEnd();
}
这将运行所有这些功能。