1. ホーム
  2. javascript

UIをブロックすることなく配列を反復処理する最良の方法

2023-08-29 05:23:45

質問

私はいくつかの大きな配列に対して反復処理を行い、APIコールからバックボーンコレクションにそれらを格納する必要があります。ループがインターフェイスを応答しなくさせることなく、これを行う最良の方法は何ですか?

返されたデータが非常に大きいので、ajaxリクエストのリターンもブロックされます。私はそれを分割して、より小さなチャンクで非同期に実行させるためにsetTimeoutを使用することができると考えていますが、これを行うためのより簡単な方法はありますか。

Web ワーカーが良いと思ったのですが、UI スレッドに保存されたいくつかのデータ構造を変更する必要があります。これを使用して ajax 呼び出しを実行しようとしましたが、UI スレッドにデータを返すときに、インターフェイスが応答しない時間がまだあります。

事前にありがとうございます。

どのように解決するのですか?

webWorkersの有無が選択できます。

WebWorkers を使用しない場合

DOM やアプリ内の他の多くの状態と対話する必要があるコードでは、webWorker を使用できないため、通常の解決策は作業をチャンクに分割し、タイマで作業の各チャンクを実行することです。 タイマーによるチャンク間の中断により、ブラウザ エンジンは進行中の他のイベントを処理することができ、ユーザーの入力が処理されるだけでなく、画面が描画されるようになります。

通常、各タイマーで 1 つ以上処理する余裕があり、タイマーごとに 1 つだけ処理するよりも効率的で高速になります。 このコードは、UI スレッドに各チャンク間の保留中の UI イベントを処理する機会を与え、UI をアクティブに維持します。

function processLargeArray(array) {
    // set this to whatever number of items you can process at once
    var chunk = 100;
    var index = 0;
    function doChunk() {
        var cnt = chunk;
        while (cnt-- && index < array.length) {
            // process array[index] here
            ++index;
        }
        if (index < array.length) {
            // set Timeout for async iteration
            setTimeout(doChunk, 1);
        }
    }    
    doChunk();    
}

processLargeArray(veryLargeArray);

このコンセプトの実用例です。この同じ関数ではなく、別の長時間実行する処理で、同じように setTimeout() を使用して、多くの反復を伴う確率シナリオをテストするための、異なる長期的なプロセスです。 http://jsfiddle.net/jfriend00/9hCVq/


上記をより汎用的にして、以下のようなコールバック関数を呼び出すことができます。 .forEach() はこのようにします。

// last two args are optional
function processLargeArrayAsync(array, fn, chunk, context) {
    context = context || window;
    chunk = chunk || 100;
    var index = 0;
    function doChunk() {
        var cnt = chunk;
        while (cnt-- && index < array.length) {
            // callback called with args (value, index, array)
            fn.call(context, array[index], index, array);
            ++index;
        }
        if (index < array.length) {
            // set Timeout for async iteration
            setTimeout(doChunk, 1);
        }
    }    
    doChunk();    
}

processLargeArrayAsync(veryLargeArray, myCallback, 100);


一度にいくつチャンクするかを推測するのではなく、経過時間を各チャンクのガイドとし、与えられた時間間隔でできる限り多く処理させることも可能です。 これにより、反復処理にどれだけCPU負荷がかかっても、ブラウザの応答性がある程度自動的に保証されます。 したがって、チャンクサイズを渡すのではなく、ミリ秒の値を渡すことができます(または、単にインテリジェントなデフォルトを使用します)。

// last two args are optional
function processLargeArrayAsync(array, fn, maxTimePerChunk, context) {
    context = context || window;
    maxTimePerChunk = maxTimePerChunk || 200;
    var index = 0;

    function now() {
        return new Date().getTime();
    }

    function doChunk() {
        var startTime = now();
        while (index < array.length && (now() - startTime) <= maxTimePerChunk) {
            // callback called with args (value, index, array)
            fn.call(context, array[index], index, array);
            ++index;
        }
        if (index < array.length) {
            // set Timeout for async iteration
            setTimeout(doChunk, 1);
        }
    }    
    doChunk();    
}

processLargeArrayAsync(veryLargeArray, myCallback);

WebWorkersを使用する

ループ内のコードが DOM にアクセスする必要がない場合、時間のかかるコードをすべて webWorker に置くことができます。 webWorker はメイン ブラウザの Javascript から独立して実行され、終了すると、postMessage ですべての結果を通信することができます。

webWorker は、webWorker で実行されるすべてのコードを別のスクリプト ファイルに分離する必要がありますが、ブラウザ内の他のイベントの処理をブロックする心配がなく、メイン スレッドで長い実行プロセスを行うときに現れるかもしれない "unresponsive script" プロンプトの心配もなく、UI のイベント処理をブロックすることなく完了するまで実行することができます。