76

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

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

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

前もって感謝します

4

4 に答える 4

120

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でイベント処理をブロックせずに発生する可能性があります。

于 2012-04-27T04:03:11.247 に答える
7

これは、この「非同期」ループを実行するデモです。反復を1ミリ秒「遅延」させ、その遅延内でUIに何かを実行する機会を与えます。

function asyncLoop(arr, callback) {
    (function loop(i) {

        //do stuff here

        if (i < arr.Length) {                      //the condition
            setTimeout(function() {loop(++i)}, 1); //rerun when condition is true
        } else { 
            callback();                            //callback when the loop ends
        }
    }(0));                                         //start with 0
}

asyncLoop(yourArray, function() {
    //do after loop  
})​;

//anything down here runs while the loop runs

Webワーカーや、現在提案されているsetImmediateのような代替手段があります。これはIE上にあり、プレフィックスが付いています。

于 2012-04-27T04:06:15.037 に答える
0

@ jfriend00に基づいて、プロトタイプバージョンを次に示します。

if (Array.prototype.forEachAsync == null) {
    Array.prototype.forEachAsync = function forEachAsync(fn, thisArg, maxTimePerChunk, callback) {
        let that = this;
        let args = Array.from(arguments);

        let lastArg = args.pop();

        if (lastArg instanceof Function) {
            callback = lastArg;
            lastArg = args.pop();
        } else {
            callback = function() {};
        }
        if (Number(lastArg) === lastArg) {
            maxTimePerChunk = lastArg;
            lastArg = args.pop();
        } else {
            maxTimePerChunk = 200;
        }
        if (args.length === 1) {
            thisArg = lastArg;
        } else {
            thisArg = that
        }

        let index = 0;

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

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

        doChunk();
    }
}
于 2017-09-26T18:22:42.393 に答える
0

どうもありがとうございました。

いくつかの機能を追加するためにコードを更新しました。

以下のコードを使用すると、配列の関数(配列を反復する)またはマップの関数(マップを反復する)のいずれかを使用できます。

また、チャンクが完了したときに呼び出される関数のパラメーター(読み込みメッセージを更新する必要がある場合に役立ちます)と、ループの処理の最後に呼び出される関数のパラメーター(次の処理に必要)があります。非同期操作が完了した後のステップ)

//Iterate Array Asynchronously
//fn = the function to call while iterating over the array (for loop function call)
//chunkEndFn (optional, use undefined if not using) = the function to call when the chunk ends, used to update a loading message
//endFn (optional, use undefined if not using) = called at the end of the async execution
//last two args are optional
function iterateArrayAsync(array, fn, chunkEndFn, endFn, 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((now() - startTime) > maxTimePerChunk && chunkEndFn !== undefined){
            //callback called with args (index, length)
            chunkEndFn.call(context,index,array.length);
        }
        if (index < array.length) {
            // set Timeout for async iteration
            setTimeout(doChunk, 1);
        }
        else if(endFn !== undefined){
            endFn.call(context);
        }
    }    
    doChunk();    
}

//Usage
iterateArrayAsync(ourArray,function(value, index, array){
    //runs each iteration of the loop
},
function(index,length){
    //runs after every chunk completes, this is optional, use undefined if not using this
},
function(){
    //runs after completing the loop, this is optional, use undefined if not using this

});

//Iterate Map Asynchronously
//fn = the function to call while iterating over the map (for loop function call)
//chunkEndFn (optional, use undefined if not using) = the function to call when the chunk ends, used to update a loading message
//endFn (optional, use undefined if not using) = called at the end of the async execution
//last two args are optional
function iterateMapAsync(map, fn, chunkEndFn, endFn, maxTimePerChunk, context) {
    var array = Array.from(map.keys());
    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, key, map)
            fn.call(context,map.get(array[index]), array[index], map);
            ++index;
        }
        if((now() - startTime) > maxTimePerChunk && chunkEndFn !== undefined){
            //callback called with args (index, length)
            chunkEndFn.call(context,index,array.length);
        }
        if (index < array.length) {
            // set Timeout for async iteration
            setTimeout(doChunk, 1);
        }
        else if(endFn !== undefined){
            endFn.call(context);
        }
    }    
    doChunk();
}

//Usage
iterateMapAsync(ourMap,function(value, key, map){
    //runs each iteration of the loop
},
function(index,length){
    //runs after every chunk completes, this is optional, use undefined if not using this
},
function(){
    //runs after completing the loop, this is optional, use undefined if not using this

});
于 2019-12-26T20:20:54.407 に答える