Theorie und Praxis: In der Theorie gibt es keinen Unterschied zwischen Theorie und Praxis, in der Praxis jedoch.
- Theorie: Alles ist klar, aber nichts funktioniert;
- Übung: Alles funktioniert, aber nichts ist klar;
- Manchmal trifft Theorie auf Praxis: Nichts funktioniert und nichts ist klar.
Manchmal ist der beste Ansatz ein Prototyp, und als ich das Problem interessant fand, verbrachte ich ein wenig Zeit damit, einen zu kochen, obwohl er als Prototyp zugegebenermaßen viele Warzen hat ...
Kurz gesagt, die einfachste Lösung zur Begrenzung eines Rückstands bei Datenabrufen scheint darin zu bestehen, einfach den Mutex eines armen Mannes innerhalb der Routine einzurichten , die das Abrufen durchführt. (Im folgenden Codebeispiel lautet die simulierte Abruffunktion simulateFetchOfData
.) Beim Mutex wird eine Variable außerhalb des Funktionsumfangs so eingerichtet, dass false
der Abruf zur Verwendung geöffnet ist und true
der Abruf gerade ausgeführt wird.
Das heißt, wenn der Benutzer den horizontalen oder vertikalen Schieberegler anpasst, um einen Datenabruf zu initiieren, prüft die Funktion, die die Daten abruft, zuerst, ob die globale Variable mutex
wahr ist (dh ein Abruf ist bereits im Gange), und wird in diesem Fall einfach beendet . Wenn dies mutex
nicht der Fall ist, wird der Wert mutex
auf true gesetzt und der Abruf fortgesetzt. Und natürlich am Ende der Abruffunktion,mutex
auf false gesetzt, so dass das nächste Benutzereingabeereignis dann die Mutexprüfung vorne durchläuft und einen weiteren Abruf durchführt ...
Ein paar Anmerkungen zum Prototyp.
- Innerhalb der
simulateFetchOfData
Funktion ist der Schlaf (100) als Versprechen konfiguriert, das die Verzögerung beim Abrufen der Daten simuliert. Dies ist mit etwas Protokollierung an der Konsole eingeklemmt. Wenn Sie die Mutex-Prüfung entfernen, werden Sie bei geöffneter Konsole feststellen, dass beim Verschieben der Schieberegler viele Instanzen vonsimulateFetchOfData
initiiert und in Spannung gesetzt werden, während der Mutex-Prüfung auf den Ruhezustand (dh den simulierten Datenabruf) wartet An Ort und Stelle wird jeweils nur eine Instanz initiiert.
- Die Ruhezeit kann angepasst werden, um eine größere Netzwerk- oder Datenbanklatenz zu simulieren, sodass Sie ein Gefühl für die Benutzererfahrung bekommen. Zum Beispiel haben Netzwerke, auf denen ich bin, eine Latenz von 90 ms für Kommunikation in den kontinentalen USA.
- Eine andere bemerkenswerte ist, dass beim Beenden eines Abrufs und nach dem Zurücksetzen
mutex
auf false eine Überprüfung durchgeführt wird, um festzustellen, ob die horizontalen und vertikalen Bildlaufwerte ausgerichtet sind. Wenn nicht, wird ein weiterer Abruf eingeleitet. Dies stellt sicher, dass trotz einer Anzahl von Bildlaufereignissen, die möglicherweise nicht ausgelöst werden, weil der Abruf beschäftigt ist, mindestens die endgültigen Bildlaufwerte durch Auslösen eines endgültigen Abrufs adressiert werden.
- Die simulierten Zellendaten sind einfach ein Zeichenfolgenwert der Zeilen-Strich-Spalten-Nummer. Beispielsweise gibt "555-333" Zeile 555, Spalte 333 an.
- Ein spärliches Array mit dem Namen
buffer
wird verwendet, um die "abgerufenen" Daten zu speichern. Wenn Sie es in der Konsole untersuchen, werden viele "leere x XXXX" -Einträge angezeigt. Die simulateFetchOfData
Funktion ist so eingerichtet, dass, wenn die Daten bereits gespeichert sind buffer
, kein "Abrufen" durchgeführt wird.
(Um den Prototyp anzuzeigen, kopieren Sie einfach den gesamten Code und fügen Sie ihn in eine neue Textdatei ein, benennen Sie ihn in ".html" um und öffnen Sie ihn in einem Browser. BEARBEITEN: Wurde auf Chrome und Edge getestet.)
<html><head>
<script>
function initialize() {
window.rowCount = 10000;
window.colCount = 5000;
window.buffer = [];
window.rowHeight = Array( rowCount ).fill( 25 ); // 20px high rows
window.colWidth = Array( colCount ).fill( 70 ); // 70px wide columns
var cellAreaCells = { row: 0, col: 0, height: 0, width: 0 };
window.contentGridCss = [ ...document.styleSheets[ 0 ].rules ].find( rule => rule.selectorText === '.content-grid' );
window.cellArea = document.getElementById( 'cells' );
// Horizontal slider will indicate the left most column.
window.hslider = document.getElementById( 'hslider' );
hslider.min = 0;
hslider.max = colCount;
hslider.oninput = ( event ) => {
updateCells();
}
// Vertical slider will indicate the top most row.
window.vslider = document.getElementById( 'vslider' );
vslider.max = 0;
vslider.min = -rowCount;
vslider.oninput = ( event ) => {
updateCells();
}
function updateCells() {
// Force a recalc of the cell height and width...
simulateFetchOfData( cellArea, cellAreaCells, { row: -parseInt( vslider.value ), col: parseInt( hslider.value ) } );
}
window.mutex = false;
window.lastSkippedRange = null;
window.addEventListener( 'resize', () => {
//cellAreaCells.height = 0;
//cellAreaCells.width = 0;
cellArea.innerHTML = '';
contentGridCss.style[ "grid-template-rows" ] = "0px";
contentGridCss.style[ "grid-template-columns" ] = "0px";
window.initCellAreaSize = { height: document.getElementById( 'cellContainer' ).clientHeight, width: document.getElementById( 'cellContainer' ).clientWidth };
updateCells();
} );
window.dispatchEvent( new Event( 'resize' ) );
}
function sleep( ms ) {
return new Promise(resolve => setTimeout( resolve, ms ));
}
async function simulateFetchOfData( cellArea, curRange, newRange ) {
//
// Global var "mutex" is true if this routine is underway.
// If so, subsequent calls from the sliders will be ignored
// until the current process is complete. Also, if the process
// is underway, capture the last skipped call so that when the
// current finishes, we can ensure that the cells align with the
// settled scroll values.
//
if ( window.mutex ) {
lastSkippedRange = newRange;
return;
}
window.mutex = true;
//
// The cellArea width and height in pixels will tell us how much
// room we have to fill.
//
// row and col is target top/left cell in the cellArea...
//
newRange.height = 0;
let rowPixelTotal = 0;
while ( newRange.row + newRange.height < rowCount && rowPixelTotal < initCellAreaSize.height ) {
rowPixelTotal += rowHeight[ newRange.row + newRange.height ];
newRange.height++;
}
newRange.width = 0;
let colPixelTotal = 0;
while ( newRange.col + newRange.width < colCount && colPixelTotal < initCellAreaSize.width ) {
colPixelTotal += colWidth[ newRange.col + newRange.width ];
newRange.width++;
}
//
// Now the range to acquire is newRange. First, check if this data
// is already available, and if not, fetch the data.
//
function isFilled( buffer, range ) {
for ( let r = range.row; r < range.row + range.height; r++ ) {
for ( let c = range.col; c < range.col + range.width; c++ ) {
if ( buffer[ r ] == null || buffer[ r ][ c ] == null) {
return false;
}
}
}
return true;
}
if ( !isFilled( buffer, newRange ) ) {
// fetch data!
for ( let r = newRange.row; r < newRange.row + newRange.height; r++ ) {
buffer[ r ] = [];
for ( let c = newRange.col; c < newRange.col + newRange.width; c++ ) {
buffer[ r ][ c ] = `${r}-${c} data`;
}
}
console.log( 'Before sleep' );
await sleep(100);
console.log( 'After sleep' );
}
//
// Now that we have the data, let's load it into the cellArea.
//
gridRowSpec = '';
for ( let r = newRange.row; r < newRange.row + newRange.height; r++ ) {
gridRowSpec += rowHeight[ r ] + 'px ';
}
gridColumnSpec = '';
for ( let c = newRange.col; c < newRange.col + newRange.width; c++ ) {
gridColumnSpec += colWidth[ c ] + 'px ';
}
contentGridCss.style[ "grid-template-rows" ] = gridRowSpec;
contentGridCss.style[ "grid-template-columns" ] = gridColumnSpec;
cellArea.innerHTML = '';
for ( let r = newRange.row; r < newRange.row + newRange.height; r++ ) {
for ( let c = newRange.col; c < newRange.col + newRange.width; c++ ) {
let div = document.createElement( 'DIV' );
div.innerText = buffer[ r ][ c ];
cellArea.appendChild( div );
}
}
//
// Let's update the reference to the current range viewed and clear the mutex.
//
curRange = newRange;
window.mutex = false;
//
// One final step. Check to see if the last skipped call to perform an update
// matches with the current scroll bars. If not, let's align the cells with the
// scroll values.
//
if ( lastSkippedRange ) {
if ( !( lastSkippedRange.row === newRange.row && lastSkippedRange.col === newRange.col ) ) {
lastSkippedRange = null;
hslider.dispatchEvent( new Event( 'input' ) );
} else {
lastSkippedRange = null;
}
}
}
</script>
<style>
/*
".range-slider" adapted from... https://codepen.io/ATC-test/pen/myPNqW
See https://www.w3schools.com/howto/howto_js_rangeslider.asp for alternatives.
*/
.range-slider-horizontal {
width: 100%;
height: 20px;
}
.range-slider-vertical {
width: 20px;
height: 100%;
writing-mode: bt-lr; /* IE */
-webkit-appearance: slider-vertical;
}
/* grid container... see https://www.w3schools.com/css/css_grid.asp */
.grid-container {
display: grid;
width: 95%;
height: 95%;
padding: 0px;
grid-gap: 2px;
grid-template-areas:
topLeft column topRight
row cells vslider
botLeft hslider botRight;
grid-template-columns: 50px 95% 27px;
grid-template-rows: 20px 95% 27px;
}
.grid-container > div {
border: 1px solid black;
}
.grid-topLeft {
grid-area: topLeft;
}
.grid-column {
grid-area: column;
}
.grid-topRight {
grid-area: topRight;
}
.grid-row {
grid-area: row;
}
.grid-cells {
grid-area: cells;
}
.grid-vslider {
grid-area: vslider;
}
.grid-botLeft {
grid-area: botLeft;
}
.grid-hslider {
grid-area: hslider;
}
.grid-botRight {
grid-area: botRight;
}
/* Adapted from... https://medium.com/evodeck/responsive-data-tables-with-css-grid-3c58ecf04723 */
.content-grid {
display: grid;
overflow: hidden;
grid-template-rows: 0px; /* Set later by simulateFetchOfData */
grid-template-columns: 0px; /* Set later by simulateFetchOfData */
border-top: 1px solid black;
border-right: 1px solid black;
}
.content-grid > div {
overflow: hidden;
white-space: nowrap;
border-left: 1px solid black;
border-bottom: 1px solid black;
}
</style>
</head><body onload='initialize()'>
<div class='grid-container'>
<div class='topLeft'> TL </div>
<div class='column' id='columns'> column </div>
<div class='topRight'> TR </div>
<div class='row' id = 'rows'> row </div>
<div class='cells' id='cellContainer'>
<div class='content-grid' id='cells'>
Cells...
</div>
</div>
<div class='vslider'> <input id="vslider" type="range" class="range-slider-vertical" step="1" value="0" min="0" max="0"> </div>
<div class='botLeft'> BL </div>
<div class='hslider'> <input id="hslider" type="range" class="range-slider-horizontal" step="1" value="0" min="0" max="0"> </div>
<div class='botRight'> BR </div>
</div>
</body></html>
Auch dies ist ein Prototyp, um ein Mittel zur Begrenzung eines Rückstands unnötiger Datenanrufe zu beweisen. Wenn dies für Produktionszwecke umgestaltet werden soll, müssen viele Bereiche adressiert werden, einschließlich: 1) Reduzierung der Nutzung des globalen variablen Raums; 2) Hinzufügen von Zeilen- und Spaltenbeschriftungen; 3) Hinzufügen von Schaltflächen zu den Schiebereglern zum Scrollen einzelner Zeilen oder Spalten; 4) möglicherweise verwandte Daten puffern, wenn Datenberechnungen erforderlich sind; 5) etc ...