20

私の具体的な問題は、バッチファイルのようなものを準備するために(潜在的に)多数のJavascript関数を実行する必要があることです(各関数呼び出しは同じバッチファイルにいくつかの情報を追加します)。バッチ ファイルを送信する最終関数 (たとえば、HTML 応答として送信)。このための一般的な Javascript プログラミング パターンを探しています。

問題の一般化: Javascript 関数 funcA()、funcB()、および funcC() が与えられた場合、funcA および funcB が実行された後にのみ funcC が実行されるように、実行を順序付ける最良の方法を見つけ出します。次のようにネストされたコールバック関数を使用できることを知っています。

funcA = function() {
    //Does funcA stuff
    funcB();
}
funcB = function() {
    //Does funcB stuff
    funcC();
}

funcA();

コールバック パラメータを渡すことで、このパターンをもう少し一般的なものにすることもできますが、この解決策は非常に冗長になります。

また、ソリューションが次のようになる Javascript 関数チェーンにも精通しています。

myObj = {}
myObj.answer = ""
myObj.funcA = function() {
    //Do some work on this.answer
    return this;
}
myObj.funcB = function() {
    //Do some more work on this.answer
    return this;
}
myObj.funcC = function() {
    //Use the value of this.answer now that funcA and funcB have made their modifications
    return this;
}
myObj.funcA().funcB().funcC();

このソリューションは私には少しすっきりしているように見えますが、計算にステップを追加すると、関数実行のチェーンがどんどん長くなります。

私の特定の問題では、funcA、funcB などが実行される順序は重要ではありません。したがって、上記のソリューションでは、すべての機能を順番に並べているため、技術的に必要以上の作業を行っています。私にとって重要なのは、funcA と funcB がすべて実行を完了した後にのみ、funcC (結果を送信したり、要求を開始したりするための関数) が呼び出されることです。理想的には、funcC は何らかの方法ですべての中間関数呼び出しをリッスンして完了し、THEN を実行できますか? このような問題を解決するための一般的な Javascript パターンを学びたいと思っています。

ご協力いただきありがとうございます。

別のアイデア: 共有オブジェクトを funcA と funcB に渡し、実行が完了したら、sharedThing.funcA = "complete" または sharedThing.funcB = "complete" のように共有オブジェクトをマークしてから、どうにかしてください。共有オブジェクトがすべてのフィールドが完了とマークされた状態に達したときに funcC を実行します。funcC にこれを待機させる方法が正確にわかりません。

編集:私はサーバー側のJavascript(Node.js)を使用していることに注意する必要があります.JQueryや他のライブラリを使用せずに、単純な古いJavascriptを使用して解決するパターンを学びたいと思います. 確かに、この問題は十分に一般的であり、きれいな純粋な Javascript ソリューションがあるのでしょうか?

4

7 に答える 7

9

シンプルに保ちたい場合は、カウンターベースのコールバック システムを使用できます。when(A, B).then(C)これは、構文を許可するシステムのドラフトです。( when/thenは実際には単なる砂糖ですが、システム全体は間違いなく砂糖です。)

var when = function() {
  var args = arguments;  // the functions to execute first
  return {
    then: function(done) {
      var counter = 0;
      for(var i = 0; i < args.length; i++) {
        // call each function with a function to call on done
        args[i](function() {
          counter++;
          if(counter === args.length) {  // all functions have notified they're done
            done();
          }
        });
      }
    }
  };
};

使用法:

when(
  function(done) {
    // do things
    done();
  },
  function(done) {
    // do things
    setTimeout(done, 1000);
  },
  ...
).then(function() {
  // all are done
});
于 2012-06-30T22:20:14.253 に答える
3

非同期関数を使用せず、スクリプトが実行順序を崩さない場合、最も簡単な解決策は、Pointy などによって述べられているように、次のとおりです。

funcA(); 
funcB();
funcC();

ただし、node.js を使用しているため、非同期関数を使用し、非同期 IO 要求が終了した後に実行したいと考えてfuncCいるため、次のようなカウント メカニズムを使用する必要があります。

var call_after_completion = function(callback){
    this._callback = callback;
    this._args = [].slice.call(arguments,1);
    this._queue = {};
    this._count = 0;
    this._run = false;
}

call_after_completion.prototype.add_condition = function(str){
    if(this._queue[str] !== undefined)
        throw new TypeError("Identifier '"+str+"' used twice");
    else if(typeof str !== "String" && str.toString === undefined)
        throw new TypeError("Identifier has to be a string or needs a toString method");

    this._queue[str] = 1;
    this._count++;
    return str;
}

call_after_completion.prototype.remove_condition = function(str){
    if(this._queue[str] === undefined){
        console.log("Removal of condition '"+str+"' has no effect");
        return;
    }
    else if(typeof str !== "String" && str.toString === undefined)
        throw new TypeError("Identifier has to be a string or needs a toString method");

    delete this._queue[str];

    if(--this._count === 0 && this._run === false){
        this._run = true;
        this._callback.apply(null,this._args);
    }
}

このオブジェクトは、識別子strを無視して増減するだけで単純化できthis._countますが、このシステムはデバッグに役立ちます。

使用するには、目的の関数を引数として を作成し、scall_after_completionを作成するだけです。すべての条件が削除された場合にのみ呼び出されます。new call_after_completionfuncadd_conditionfunc

例:

var foo = function(){console.log("foo");}
var bar = new call_after_completion(foo);
var i;

bar.add_condition("foo:3-Second-Timer");
bar.add_condition("foo:additional function");
bar.add_condition("foo:for-loop-finished");

function additional_stuff(cond){
    console.log("additional things");
    cond.remove_condition("foo:additional function");
}

for(i = 0; i < 1000; ++i){

}
console.log("for loop finished");
bar.remove_condition("foo:for-loop-finished");
additional_stuff(bar);

setTimeout(function(){
    console.log("3 second timeout");
    bar.remove_condition("foo:3-Second-Timer");
},3000);

JSFiddleデモ

于 2012-06-30T22:55:10.563 に答える
2

ヘルパー ライブラリを使用したくない場合は、自分でヘルパーを作成する必要があります。これに対する単純な 1 行のソリューションはありません。

同期の場合と同じくらい読みやすいもので終了したい場合は、遅延/約束の概念の実装を試してください (それはまだプレーンな JavaScript です)。たとえば、deferredパッケージを使用すると、次のような単純なものになる可能性があります。

// Invoke one after another:
funcA()(funcB)(funcC);

// Invoke funcA and funcB simultaneously and afterwards funcC:
funcA()(funcB())(funcC);

// If want result of both funcA and funcB to be passed to funcC:
deferred(funcA(), funcB())(funcC);
于 2012-07-12T19:18:05.783 に答える
0

同じ柄を探していました。複数のリモート データ ソースに問い合わせる API を使用しています。API ごとに、コールバック関数を渡す必要があります。これは、一連の独自の関数を起動して、それらが返されるのを待つことができないことを意味します。代わりに、さまざまなデータ ソースの応答性に応じて任意の順序で呼び出される一連のコールバックで動作するソリューションが必要です。

次の解決策を思いつきました。JS は、私が最もよく知っている言語のリストのずっと下にあるため、これは JS のイディオムではないかもしれません。

function getCallbackCreator( number_of_data_callbacks, final_callback ) {

    var all_data = {}

    return function ( data_key ) {

        return function( data_value ) {
            all_data[data_key] = data_value;

            if ( Object.keys(all_data).length == number_of_data_callbacks ) {
                final_callback( all_data );
            }
        }
    }
}

var getCallback = getCallbackCreator( 2, inflatePage );

myGoogleDataFetcher( getCallback( 'google' ) );
myCartoDataFetcher( getCallback( 'cartodb' ) );

編集:質問はnode.jsでタグ付けされましたが、OPは「これのための一般的なJavascriptプログラミングパターンを探しています」と言ったので、ノードを使用していなくてもこれを投稿しました。

于 2015-09-29T14:59:19.503 に答える
0

jQuery の遅延オブジェクトを調べてください。これにより、非同期環境で何が起こるかを制御する洗練された手段が提供されます。

これの明らかな使用例は AJAX ですが、これに限定されません。

資力:

于 2012-06-30T22:05:16.727 に答える
0

現在、次のようなことができます。

funcA、funcB、funcC の両方があるとします。

funcA と funcB の結果を funcC に渡したい場合:

var promiseA = new Promise((resolve, reject) => {
  resolve(await funcA());
});
var promiseB = new Promise((resolve, reject) => {
  resolve(await funcB());
});
var promise = Promise.all([ promiseA, promiseB ]).then(results => {
  // results = [result from funcA, result from funcB]
  return funcC(results);
});

funcA が必要な場合は、次に funcB、次に funcC が必要です。

var promise = (
  new Promise(async resolve => resolve( await funcA() ))
).then(result_a => funcB(result_a)).then(result_b => funcC(result_b));

そして最後に:

promise.then(result_c => console.log('done.'));
于 2020-11-05T15:08:55.403 に答える