2010-12-21 21 views
5

JavaScriptで本当に基本的なフロー制御を書く方法を教えてください。ありがとうございました。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."); 

}); 
+3

...あなたがしたいことをより詳しく説明できますか? – deceze

+0

私はこのフローコントロールを書いてみたいですが、どうすればいいのかわかりません。私は多くのコードを閲覧しましたが、私はそれを取得しませんでした。 –

+0

関数は潜在的に非同期ですか? – slebetman

答えて

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

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

これは、すべてのこれらの機能を実行します。

+0

しかし、非同期の場合は指定された順序ではありません。 – slebetman

+0

はい。私はこれらの機能を指定された順序で実行することを躊躇します。 –

+0

@slebetman私のコードの仕組みは、すべての機能が同期していることです。 –

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

この回答は短く、エレガントで、正確です。 –

+0

var f = last達成するのは何ですか? – qwertymk

+1

@qwertymk: 'f'は' last'で初期化され、そこから構築されます。まず、それは「最後」です。 makefunc(fns [n-2]、makefunc(fns [n-1]、last)) 'などの後に' makefunc(fns [n-1]、last) 'が続きます。 [cons list](http://en.wikipedia.org/wiki/Cons)。ここで、 'makefunc'はconsであり、lastは' nil 'です。 –

1

私は最近のプロジェクトでかなりのことをしています。私はそれを管理するためにいくつかのコードを書いた。ここにコードがあります。 bundledAsync関数には、 "calls"パラメータと "bundleCallback"パラメータを持つオブジェクトを渡します。 callsパラメータは、呼び出す関数を表すオブジェクトの配列です。 fnパラメータでは、実際のパラメータへの参照を格納します。 "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); 
    } 
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が短くかつ単純です。ここで

は、それが動作する方法は次のとおりです(いずれも配列に残っていない場合、または、funcDone)は、i 番目の関数を呼び出すコールバックを返しproceed(i)proceed(i)はコールバックを行うのではなくコールバックを返すので、を連結機能として使用できます。

使用例:

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();呼び出しの1つを取り外してみてください。それはおそらくまさにあなたが望むものであろうチェーンを壊すでしょう。もう一つのすばらしいことは、cbを取り込み、グローバル変数に入れて、後で呼び出してフローを再開することです。

このアプローチには欠点があります。多くのJavaScriptエディターは、末尾再帰を最適化しません。 funcArrが長すぎると、スタックオーバーフローが発生することがあります。これは、JavaScriptで継続継承スタイルを使用することによって、クロスキャストされます。

関連する問題