Ich muss einige große Arrays durchlaufen und sie in Backbone-Sammlungen eines API-Aufrufs speichern. Was ist der beste Weg, um dies zu tun, ohne dass die Schleife dazu führt, dass die Schnittstelle nicht mehr reagiert?
Die Rückgabe der Ajax-Anforderung wird ebenfalls blockiert, da die zurückgegebenen Daten so groß sind. Ich denke, ich könnte es aufteilen und setTimeout verwenden, um es in kleineren Blöcken asynchron laufen zu lassen, aber es gibt einen einfacheren Weg, dies zu tun.
Ich dachte, ein Web-Worker wäre gut, aber er muss einige Datenstrukturen ändern, die im UI-Thread gespeichert sind. Ich habe versucht, damit den Ajax-Aufruf auszuführen, aber wenn die Daten an den UI-Thread zurückgegeben werden, reagiert die Schnittstelle immer noch nicht mehr.
Danke im Voraus
quelle
Antworten:
Sie haben die Wahl zwischen mit oder ohne webWorkers:
Ohne WebWorker
Für Code, der mit dem DOM oder mit vielen anderen Status in Ihrer App interagieren muss, können Sie keinen WebWorker verwenden. Die übliche Lösung besteht darin, Ihre Arbeit in Blöcke aufzuteilen, um jeden Teil der Arbeit an einem Timer auszuführen. Die Unterbrechung zwischen Blöcken mit dem Timer ermöglicht es der Browser-Engine, andere Ereignisse zu verarbeiten, die gerade stattfinden, und ermöglicht nicht nur die Verarbeitung von Benutzereingaben, sondern auch das Zeichnen des Bildschirms.
Normalerweise können Sie es sich leisten, mehr als einen auf jedem Timer zu verarbeiten, was sowohl effizienter als auch schneller ist als nur einen pro Timer. Dieser Code gibt dem UI-Thread die Möglichkeit, ausstehende UI-Ereignisse zwischen jedem Block zu verarbeiten, wodurch die UI aktiv bleibt.
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);
Hier ist ein funktionierendes Beispiel für das Konzept - nicht dieselbe Funktion, sondern ein anderer Prozess mit langer Laufzeit, der dieselbe
setTimeout()
Idee verwendet, um ein Wahrscheinlichkeitsszenario mit vielen Iterationen zu testen: http://jsfiddle.net/jfriend00/9hCVq/Sie können das oben Genannte in eine allgemeinere Version umwandeln, die eine Rückruffunktion wie
.forEach()
folgt aufruft :// 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);
Anstatt zu erraten, wie viele Chunks gleichzeitig benötigt werden, ist es auch möglich, die verstrichene Zeit als Leitfaden für jeden Chunk zu verwenden und so viele wie möglich in einem bestimmten Zeitintervall verarbeiten zu lassen. Dies garantiert automatisch die Reaktionsfähigkeit des Browsers, unabhängig davon, wie CPU-intensiv die Iteration ist. Anstatt eine Blockgröße zu übergeben, können Sie einen Millisekundenwert übergeben (oder einfach einen intelligenten Standard verwenden):
// 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);
Mit WebWorkern
Wenn der Code in Ihrer Schleife nicht auf das DOM zugreifen muss, können Sie den gesamten zeitaufwändigen Code in einen WebWorker einfügen. Der webWorker wird unabhängig vom Hauptbrowser Javascript ausgeführt und kann dann, wenn er fertig ist, alle Ergebnisse mit einer postMessage zurückmelden.
Ein WebWorker muss den gesamten Code, der im WebWorker ausgeführt wird, in eine separate Skriptdatei aufteilen. Er kann jedoch vollständig ausgeführt werden, ohne dass die Verarbeitung anderer Ereignisse im Browser blockiert werden muss und ohne dass die Eingabeaufforderung "Nicht reagierendes Skript" erforderlich ist Dies kann auftreten, wenn ein lang laufender Prozess im Hauptthread ausgeführt wird und die Ereignisverarbeitung in der Benutzeroberfläche nicht blockiert wird.
quelle
.forEach()
Stilrückruf funktioniert, sodass dieselbe Dienstprogrammfunktion für viele Zwecke verwendet werden kann.for..in
Objektaufzählung zutreffen ? Erstellen Sie ein Array, dann die oben genannten? (Oder besser eine neue Frage stellen?)Object.keys()
zum Erstellen des Arrays verwenden), da Sie auf diese Weise nicht direkt mit iterieren könnenfor/in
.window.requestAnimationFrame()
anstelle von asetTimeout()
ist viel besser. Auf diese Weise sind Sie absolut sicher, dass Ihr Code nicht blockiert, da der Browser Ihnen sagt, dass es cool ist, etwas zu verarbeiten.Hier ist eine Demo dieser "asynchronen" Schleife. Es "verzögert" die Iteration um 1 ms und innerhalb dieser Verzögerung gibt es der Benutzeroberfläche die Möglichkeit, etwas zu tun.
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
Es gibt Alternativen wie Web-Worker und das derzeit vorgeschlagene setImmediate, das afaik im IE mit einem Präfix ist.
quelle
setTimeout
Funktion stellt die Funktion in die Rückrufwarteschlange, die abgeholt wird, nachdem die Benutzeroberfläche die Möglichkeit hatte, ihre Aufgabe zu erledigen. Sie können genauso einfach eine Verzögerung von 0 Sekunden eingeben.Aufbauend auf @ jfriend00 ist hier eine Prototypversion:
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(); } }
quelle
if
if (...) return; ...
Vielen Dank dafür.
Ich habe den Code aktualisiert, um einige Funktionen hinzuzufügen.
Mit dem folgenden Code können Sie entweder die Funktion für Arrays (zum Iterieren von Arrays) oder die Funktion für Karten (zum Iterieren von Karten) verwenden.
Außerdem gibt es jetzt einen Parameter für eine Funktion, die aufgerufen wird, wenn ein Block abgeschlossen ist (hilft, wenn Sie eine Lademeldung aktualisieren müssen), sowie einen Parameter für eine Funktion, die am Ende der Verarbeitung der Schleife aufgerufen wird (erforderlich, um die nächste auszuführen Schritt nach Abschluss der asynchronen Operationen)
//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 });
quelle
async
ist wohl irreführend. Die Operationen sind immer noch auf demselben Thread synchron.