hoe wandel je asycn over de keys in een object met op 1 niveau meer dan 1000 keys?

Status
Niet open voor verdere reacties.

rajmv

Gebruiker
Lid geworden
13 jul 2011
Berichten
112
M'n htmlMicroscope (verbetering voor var_dump() uit PHP), bevriest de browser als je een sub-object/array openklapt met daarin meer dan een paar honderd keys..

Dit omdat er in een loop over die keys gewandeld word, en dan een stapeltje html per key wordt opgebouwd.

Laat ik het even simpel houden;

Code:
for (var k in data) {
  var it = data[k];
  CPU_intensive_task (it);
}

Ik vraag me ernstig af hoe je zo'n loop kan laten pauzeren met setTimeout om de paar tientallen items, omdat dan de browser niet meer bevriest..
 
Dit is een betere omschrijving van het probleem;

Code:
function printLevel : function (data) {
  var html = '';
  if (typeof data=='object' && data!==null && data!==undefined) {
    for (var k in data) {  
      // and the problem is you'd have up to several thousand var k's. the loop needs to setTimeout after say a hundred iterations through THIS loop...
      var v = data[k];
      html += '<tr><td>'+printVariable(k)+'</td><td>'+printVariable (v)+'</td></tr>';
    }
  }
  return html;
}

function printVariable : function (data) {
   var t = typeof data;
   switch (t) {
     case 'number' : 
     case 'string' : 
     case 'boolean':
           return data;
           break;
     case 'object':
         return printLevel (data);
         break;
   }
}
 
De for-loop kun je helaas niet zomaar onderbreken. Wat misschien wel werkt, is dit:
- Verzamel eerst alle keys (lijkt me niet zo CPU-intensief, en anders is je object gewoon te groot :P)
- Splits die verzameling op door steeds een stukje te verwerken, en dan een timer te zetten
Nadeel is dat je dan weer asynchroon werkt, dus moet je even zorgen dat je een callback hebt voor als de taak klaar is ;)

[JS]function printLevel(data, callback) {

var html = "";
var keys = Object.keys(data);
var i = 0, N = 20, pauseLength = 500;

function printKeys() {
for (var j = 0; j < N && i + j < keys.length; j++) {
var k = keys[i + j];
html += '<tr><td>' + printVariable(k) + '</td><td>' + printVariable(data[k]) + '</td></tr>';
}
i += N;

if (i < keys.length) {
setTimeout(printKeys, pauseLength);
}
else {
callback(html);
}
}

printKeys();
}[/JS]

Edit:
Ik vroeg me trouwens af: zowel in deze als in je andere post (over clonen) is de constructie zo dat de taak die uitgevoerd moet worden, herhaaldelijk onderbroken wordt met een setTimeout. Als je setTimeout slechts één keer aanroept, echter, wordt de taak ook gewoon asynchroon uitgevoerd. Doet dat je browser dan ook bevriezen? Ofwel: heb je echt baat bij het meerdere malen gebruiken van setTimeout tegenover éénmalig gebruik?
[JS](function() {

function doAsync(f, callback, delay) {
setTimeout(function() {
var result = f();
if (callback) {
callback(result);
}
}, delay || 0);
}
function partial(f /*, args*/) {
var args = Array.prototype.slice.call(arguments, 1);
return function() {
var _args = [], i = 0, j = 0;
while (i < args.length || j < arguments.length) {
_args = args !== undefined ? args : arguments[j++];
i++;
}
return f.apply(this, _args);
};
}

/*
function clone(obj) {
var c = new obj.constructor();
for (var k in obj) {
c[k] = typeof obj[k] == "object" && obj[k] != null ? clone(obj[k]) : obj[k];
}
return c;
}
function cloneCallback(original, clone) {
console.log(original, clone, original[3] === clone[3]); //false
}
var someLargeObject = ["A", "B", 6, {
c: "C"
}];
doAsync(partial(clone, someLargeObject), partial(cloneCallback, someLargeObject));
*/

function printLevel(data) {
/* Original printLevel function */
}
function printCallback(html) {
console.log("Done");
}
var data = {};
doAsync(partial(printLevel, data), printCallback);

})();[/JS]
 
Laatst bewerkt:
De for-loop kun je helaas niet zomaar onderbreken. Wat misschien wel werkt, is dit:
- Verzamel eerst alle keys (lijkt me niet zo CPU-intensief, en anders is je object gewoon te groot :P)
- Splits die verzameling op door steeds een stukje te verwerken, en dan een timer te zetten
Nadeel is dat je dan weer asynchroon werkt, dus moet je even zorgen dat je een callback hebt voor als de taak klaar is ;)

Ga ik morgen eens naar kijken, het zou kunnen dat dat voorlopig goed genoeg werkt idd.

Edit:
Ik vroeg me trouwens af: zowel in deze als in je andere post (over clonen) is de constructie zo dat de taak die uitgevoerd moet worden, herhaaldelijk onderbroken wordt met een setTimeout. Als je setTimeout slechts één keer aanroept, echter, wordt de taak ook gewoon asynchroon uitgevoerd. Doet dat je browser dan ook bevriezen? Ofwel: heb je echt baat bij het meerdere malen gebruiken van setTimeout tegenover éénmalig gebruik?

Wat ik vanmiddag met die andere set routines van bijna het zelfde type heb geleerd is dat je setTimeout om de paar honderd iteraties moet uitvoeren, niet veel minder dan dat (want dat vertraagt het gehele proces), en niet veel meer dan dat (want dat bevriest de browser).

Een delay van 50 milliseconden elke 107 iteraties (107 is priemgetal, handig) was genoeg om me van m'n bevriezings probleem af te helpen bij het clonen van de data voor gebruik in htmlMicroscope (wat nodig is omdat hm de data mollesteert om er die collapsable-viewer van te maken).

Maar het opbouwen van de "echte" html voor een key,value pair is veel CPU intensiever dan een simpele clone van dat key,value pair. Waar het clonen goed gaat zelfs met 1500 keys in een sub-object, zal dat als je html gaat opbouwen voor al die keys dus NIET goed gaan, zoals mijn htmlMicroscope nu in zowel chrome als firefox bewijst.

't goede nieuws is dat als je die setTimeout "verstandig" erin mixed, dan zal het nu in win8+ie10 ook goed gaan.

win7+ie9 zal het mischien niet doen trouwens..

je kan de win8 preview gratis downloaden (ff googlen), de upgrade van win7 was voor mij een beetje zoals een ouwe re-install, er moesten een hoop apps opnieuw geinstalleerd en geconfigged worden.
maar win8 is wel weer sneller dan win7.
 
Laatst bewerkt:
Ik ben er uit denk ik :-)

De hieronder volgende functies scannen echt-asycnhroon een recursief object tot op een bepaald niveau (levelsAtOnce) en bouwt een flat-list (array van slechts 1 niveau diep) met daarin in de goede volgorde de datanodes (subarrays, key-value pairs) die verwerkt moeten worden tot HTML.

Die flat-list vervolgens asynchroon opbouwen tot een HTML string zal ook niet echt een probleem vormen.
Ga ik vanavond / morgen aan verder.

Notitie; deze routines werken op een data structuur die er als volgt uit ziet, je zal 't een beetje moeten aanpassen voor een normale recursieve array/object.
Code:
{
  hmData : {
       mijnDataA : 'a',
       mijnDataB : 'b',
       mijnSubKeyA : {
             hmData : {
                 mijnDataX : 'x'
             },
             hmSettings : {
                // app-specifieke waarden voor het object met 'mijnDataX' als key
             }
       }
  },
  hmSettings : {
     // app-specifieke berekende waarden voor hmData met keys mijnDataA, mijnDataB en mijnSubKeyA
  }
}

Code:
		printNextLevel : function (pvCmd) {
			if (
				typeof pvCmd.val == 'object' 
				&& typeof pvCmd.val.hmStats == 'object' 
				&& typeof pvCmd.val.hmData == 'object'
			) {
				//if (pvCmd.keyValueName && pvCmd.keyValueName!='') pvCmd.val.hmStats.keyValueName=pvCmd.keyValueName; //bit of a hack, i agree.
				var td = typeof pvCmd.val.hmData;
				var d = pvCmd.val.hmData;
			} else {
				var td = typeof pvCmd.val;
				var d = pvCmd.val;
			};
			
                        pvCmd.scanPointer = d;
			rajmv.hms.tools.printNextLevel_scan (pvCmd, rajmv.hms.tools.printNextLevel_buildDatanodes);
			
			return {
				html : '<tr><td>Have Yet To Render This</td></tr>'
			};
		},
		
		printNextLevel_scan : function (pvCmd, callback) {
			if (!pvCmd.scanResults) {
				pvCmd.scanResults = [{level:1, datanode:pvCmd.scanPointer}];
				pvCmd.scanIdx = 0;
				pvCmd.scanCount = 0;
				pvCmd.lastPause = 0;
				pvCmd.scanCallback = callback;
			}
			
			rajmv.hms.tools.printNextLevel_scanItem (pvCmd);
			pvCmd.scanCount++;
			
			//rajmv.log (2, 'pvCmd.scanIdx='+pvCmd.scanIdx+', scanResults.length-1='+(pvCmd.scanResults.length-1));
			if (pvCmd.scanIdx==pvCmd.scanResults.length-1) {
				if (typeof pvCmd.scanCallback=='function') {
					pvCmd.scanCallback (pvCmd);
				} 
				return true; // scanning done!
			}
			
			var pauseFactor = pvCmd.scanCount / 7;
			if (pauseFactor > pvCmd.lastPause + 1) {
				setTimeout (function () {
					pvCmd.lastPause = pauseFactor;
					rajmv.hms.tools.printNextLevel_scan(pvCmd);
				}, 50);
			} else {
				rajmv.hms.tools.printNextLevel_scan(pvCmd);
			};
			return false; // not done yet
		},
		
		printNextLevel_scanItem : function (pvCmd) {
			var it = pvCmd.scanResults[pvCmd.scanIdx];
			if (!it || !it.datanode) return false;
			var tit = typeof it.datanode;
			if (tit=='object' && it.datanode!==null && it.datanode!==undefined) {
				if (!it.keys && it.level<=pvCmd.levelsAtOnce) {
					it.keys = Object.keys (it.datanode);
					it.keyIdx = 0;
				}
			}
			if (it.keys) {
				if (it.keyIdx<it.keys.length) {
					var doUntil = it.keyIdx+20;
					while (it.keyIdx<doUntil && it.keyIdx<it.keys.length-1) {
						rajmv.hms.tools.printNextLevel_scanKey (pvCmd);
						it.keyIdx++;
						pvCmd.scanCount++;
						
						if (it.keyIdx==it.keys.length-1) {
							pvCmd.scanIdx++;
							pvCmd.scanCount++;
						}

						var pauseFactor = pvCmd.scanCount / 7;
						if (pauseFactor > pvCmd.lastPause + 1) break;
					}
				}
			} else {
				pvCmd.scanIdx++;
				pvCmd.scanCount++;
			}
		},
		
		printNextLevel_scanKey : function (pvCmd) {
			var it = pvCmd.scanResults[pvCmd.scanIdx];
			if (!it.keys[it.keyIdx]) debugger;
			pvCmd.scanResults.splice(pvCmd.scanIdx+1, 0, {level:it.level+1, datanode:it.datanode[it.keys[it.keyIdx]].hmData}); // insert next datanode just after the scanIdx
			rajmv.log (2, 'hms.tools.printNextLevel_scanKey(): pvCmd.scanCount='+pvCmd.scanCount);
		},
		
		printNextLevel_buildDatanodes : function (pvCmd) {
			if (!pvCmd.html) pvCmd.html = '';
			
			debugger;
			
		},
 
Laatst bewerkt:
Status
Niet open voor verdere reacties.
Terug
Bovenaan Onderaan