Scheduler
I’ve recently became aware of the new Scheduler API. The cool thing about this api is that it enables a long running loop to yield.
As you know in javascript, if you call a function, the ui will not be able to refresh or respond to user input until that function is fully done.
Frontend apps by default should always be responsive to user input/requests and should not freeze.
Below is a demo of scheduler.yield(). Use your arrow keys to move the highlighted box. You should notice that your key navigation request is still
working while the long for loop is going on as well.
References
https://developer.mozilla.org/en-US/docs/Web/API/Scheduler
https://github.com/astoilkov/main-thread-scheduling
Source
const ELEMENTS = 1000 ;
const ITER = Number.MAX_SAFE_INTEGER;
const INTERACTION_COLS = 20
const INTERACTION_ROWS = 6
const root = document. getElementById ( 'demoroot' )
const interRoot = document. getElementById ( 'interroot' )
let activeRow = 0
let activeCol = 0
let lastActiveRow = - 1
let lastActiveCol = - 1
for ( let i = 0 ; i < INTERACTION_ROWS ; i ++ ) {
const row = document. createElement ( 'div' )
row. setAttribute ( 'class' , 'flex' )
interRoot?. appendChild (row)
for ( let j = 0 ; j < INTERACTION_COLS ; j ++ ) {
const cell = document. createElement ( 'div' )
cell. setAttribute ( 'class' , 'w-6 h-6 border border-slate-100' )
cell. setAttribute ( 'id' , `cell-${ i }-${ j }` )
row. appendChild (cell)
}
}
function highlightActive ( row : number , col : number , prevRow : number , prevCol : number ) {
const cell = document. getElementById ( `cell-${ row }-${ col }` ) as HTMLDivElement
if (cell) {
cell.style.background = 'pink'
}
const lastActiveCell = document. getElementById ( `cell-${ prevRow }-${ prevCol }` ) as HTMLDivElement
if (lastActiveCell && lastActiveCell !== cell) {
lastActiveCell.style.background = 'transparent'
}
}
highlightActive (activeRow, activeCol, lastActiveRow, lastActiveCol)
lastActiveCol = activeCol
lastActiveRow = activeRow
document. addEventListener ( 'keydown' , ( e ) => {
e. preventDefault ()
if (e.code === 'ArrowDown' && activeRow + 1 < INTERACTION_ROWS ) {
lastActiveRow = activeRow
lastActiveCol = activeCol
activeRow += 1
}
if (e.code === 'ArrowRight' && activeCol + 1 < INTERACTION_COLS ) {
lastActiveCol = activeCol
lastActiveRow = activeRow
activeCol += 1
}
if (e.code === 'ArrowUp' && activeRow - 1 > - 1 ) {
lastActiveRow = activeRow
lastActiveCol = activeCol
activeRow -= 1
}
if (e.code === 'ArrowLeft' && activeCol - 1 > - 1 ) {
lastActiveRow = activeRow
lastActiveCol = activeCol
activeCol -= 1
}
highlightActive (activeRow, activeCol, lastActiveRow, lastActiveCol)
})
if (root) {
const colors = [];
for ( let i = 0 ; i < ELEMENTS ; i ++ ) {
await (window as any ) . yield ();
const color = `${ Math . floor ( Math . random () * 16777215 )
. toString ( 16 )
. padStart ( 6 , '0' ) }` ;
colors. push (color);
}
for ( let j = 0 ; j < ITER ; j ++ ) {
for ( let i = 0 ; i < colors. length ; i ++ ) {
await (window as any ).scheduler. yield ();
const id = colors[i];
let el = document. getElementById (id);
if (el) {
el.style.background = `#${ Math . floor ( Math . random () * 16777215 )
. toString ( 16 )
. padStart ( 6 , '0' ) }` ;
} else {
el = document. createElement ( 'div' );
el.style.width = '10px' ;
el.style.height = '10px' ;
el.style.background = `#${ id }` ;
el. setAttribute ( 'id' , id);
root. appendChild (el);
}
}
}
}