UI をブロックせずに配列を反復処理する最良の方法 質問する

UI をブロックせずに配列を反復処理する最良の方法 質問する

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

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

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

前もって感謝します

ベストアンサー1

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

WebWorkersなし

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

通常、各タイマーで複数の処理を行うことができます。これは、タイマーごとに 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 で実行されるすべてのコードを別のスクリプト ファイルに分離する必要がありますが、ブラウザー内の他のイベントの処理をブロックする心配がなく、メイン スレッドで長時間実行されるプロセスを実行するときに表示される「応答のないスクリプト」プロンプトを心配する必要もなく、UI でイベント処理をブロックすることなく、完了まで実行できます。

おすすめ記事