13

私はかなり長い間 JavaScript で開発を行ってきましたが、常に頭を悩ます多くのことの 1 つは JavaScript のコールバックを同期させることであり、純然たるカウボーイ開発者です。

この問題が発生する一般的なシナリオについて説明します。for ループで複数回実行する一連の操作があり、各操作にはコールバックがあります。for ループの後、別の操作を実行する必要がありますが、この操作は for ループからのすべてのコールバックが完了した場合にのみ正常に実行されます。

コード例:

 for ... in ... {
   myFunc1(callback); // callbacks are executed asynchly
 }

 myFunc2(); // can only execute properly if all the myFunc1 callbacks are done

推奨される解決策:

ループの長さを保持するループの開始時にカウンターを開始し、各コールバックがそのカウンターをデクリメントします。カウンターが 0 になったら、myFunc2 を実行します。これは本質的に、コールバックがシーケンスの最後のコールバックであるかどうかをコールバックに知らせ、そうである場合は完了時に myFunc2 を呼び出すためです。

問題:

  1. コード内のそのようなシーケンスごとにカウンターが必要であり、無意味なカウンターをいたるところに配置することはお勧めできません。
  2. 複数のスレッドがすべて同じ var で var-- を呼び出している場合、従来の同期問題でスレッドがどのように競合するかを思い出すと、望ましくない結果が発生します。JavaScriptでも同じことが起こりますか?

究極の質問:

より良い解決策はありますか?

4

6 に答える 6

13

幸いなことに、JavaScript はシングル スレッドです。これは、ソリューションが一般に「共有」変数でうまく機能することを意味します。つまり、ミューテックス ロックは必要ありません。

非同期タスクをシリアル化し、その後に完了コールバックを続けたい場合は、次のヘルパー関数を使用できます。

function serializeTasks(arr, fn, done)
{
    var current = 0;

    fn(function iterate() {
        if (++current < arr.length) {
            fn(iterate, arr[current]);
        } else {
            done();
        }
    }, arr[current]);
}

最初の引数は各パスで渡す必要がある値の配列で、2 番目の引数はループ コールバック (以下で説明) で、最後の引数は完了コールバック関数です。

これはループ コールバック関数です。

function loopFn(nextTask, value) {
    myFunc1(value, nextTask);
}

渡される最初の引数は、次のタスクを実行する関数であり、非同期関数に渡されることを意図しています。2 番目の引数は、値の配列の現在のエントリです。

非同期タスクが次のようになっているとします。

function myFunc1(value, callback)
{
  console.log(value);
  callback();
}

値を出力し、その後コールバックを呼び出します。単純。

次に、全体を動かします。

serializeTasks([1,2, 3], loopFn, function() {
    console.log('done');
});

デモ

それらを並列化するには、別の関数が必要です。

function parallelizeTasks(arr, fn, done)
{
    var total = arr.length,
    doneTask = function() {
      if (--total === 0) {
        done();
      }
    };

    arr.forEach(function(value) {
      fn(doneTask, value);
    });
}

ループ関数は次のようになります (パラメーター名のみが変更されます)。

function loopFn(doneTask, value) {
    myFunc1(value, doneTask);
}

デモ

于 2013-04-12T06:33:39.220 に答える
3

2番目の問題は、それらのすべてが別の関数にあり、変数が正しく宣言されている限り( でvar)、実際には問題ではありません。関数内のローカル変数は互いに干渉しません。

最初の問題はもう少し問題です。他の人もイライラして、そのようなパターンをラップするライブラリを作成してしまいました。私は好きasyncです。これを使用すると、コードは次のようになります。

async.each(someArray, myFunc1, myFunc2);

他の多くの非同期ビルディング ブロックも提供します。多くの非同期処理を行っている場合は、これを参照することをお勧めします。

于 2013-04-12T05:21:22.667 に答える
2

これは、jQuery 遅延オブジェクトを使用して実現できます。

var deferred = $.Deferred();
var success = function () {
    // resolve the deferred with your object as the data
    deferred.resolve({
        result:...;
    });
};
于 2013-04-12T05:27:35.683 に答える
1

このヘルパー関数を使用すると:

function afterAll(callback,what) {
  what.counter = (what.counter || 0) + 1;
  return function() {
    callback(); 
    if(--what.counter == 0) 
      what();
  };
}

ループは次のようになります。

function whenAllDone() { ... }
for (... in ...) {
  myFunc1(afterAll(callback,whenAllDone)); 
}

ここでafterAllは、コールバックのプロキシ関数を作成し、カウンターもデクリメントします。そして、すべてのコールバックが完了すると whenAllDone 関数を呼び出します。

于 2013-04-12T06:24:18.030 に答える