最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Basic Flow Control in JavaScript - Stack Overflow

programmeradmin2浏览0评论

Could you, please, explain to me, how to write really basic flow control in JavaScript? Thank you.

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

});

Could you, please, explain to me, how to write really basic flow control in JavaScript? Thank you.

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

});
Share Improve this question edited Dec 21, 2010 at 1:46 Joey Adams 43.5k19 gold badges88 silver badges115 bronze badges asked Dec 21, 2010 at 1:28 Josh NewtonJosh Newton 513 bronze badges 8
  • 3 ...could you please explain in more detail what you want to do? – deceze Commented Dec 21, 2010 at 1:31
  • I would like to write this flow control, but I don't know how. I browsed through lots of code, but I didn't get it. – Josh Newton Commented Dec 21, 2010 at 1:34
  • Are the functions potentially asynchronous? – slebetman Commented Dec 21, 2010 at 1:42
  • 1 Is this dude a friend of yours? stackoverflow./questions/4495110/flow-control-in-javascript/… – Pointy Commented Dec 21, 2010 at 1:45
  • @Josh But you said below that you want them to run sequentially... – Šime Vidas Commented Dec 21, 2010 at 1:46
 |  Show 3 more ments

5 Answers 5

Reset to default 7

Would something like this work?

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

I've been doing quite a bit of that in a recent project. I've written some code to help manage it. Here's the code. You pass the bundledAsync function an object with a "calls" parameter and a "bundleCallback" parameter. The calls parameter is an array of objets representing the function you want to call. In the fn param, you store a reference to the actual parameter. In the "args" param, you store your arguments. The last argument of each of the functions you pass in must be a callback, which must be called.

I'm miserable at documenting my code and making it useful to others, but this is really really useful to me. I suspect someone else has written something similar, perhaps properly documented. If you can't find it, and need help figuring this out, let me know.

/**
  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 pleted,
              // 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);
  }

I remend reading about continuation-passing style. It seems your goal is, given an array of functions which take a continuation argument, chain them together such that the continuation proceeds to the next function in the array.

Here is an implementation of such a function:

function flow(funcArr, funcDone) {
    function proceed(i) {
        if (i < funcArr.length) {
            return function() {
                funcArr[i](proceed(i+1));
            }
        } else {
            return funcDone;
        }
    }

    proceed(0)();
}

Edit: Anon.'s answer is shorter and simpler.

Here's how it works: proceed(i) returns a callback which calls the ith function (or, funcDone, if none are left in the array). Because proceed(i) returns a callback rather than doing it, we can use proceed(i+1) as the contiunation function.

Sample usage:

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

});

Now try removing one of the cb(); calls. It will break the chain, which is probably exactly what you want. Another cool thing is, you can take cb, shove it in a global variable, and call it later on to resume the flow.

Bear in mind there is a drawback to this approach: many (if not all) JavaScript interpreters don't optimize tail recursion. If the funcArr is too long, you may get a stack overflow. This is a cross borne by any use of continuation-passing style in JavaScript.

(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)));
})()
// callback is a global function, I assume

function flow(funcArr, funcEnd) {    
    for (var i = 0; i < funcArr.length; i++) {
        funcArr[i](callback);
    }    
    funcEnd();
}

This would run all those functions.

发布评论

评论列表(0)

  1. 暂无评论