Fast Data Grid features:
- Incredibly Fast
- Multithreaded
- Only 523 Lines of Code
- No Dependencies
- Vanilla JavaScript
Try scrolling and searching through 1,000,000 rows — Fast Data Grid.
In this article I will talk about working with threads.
The main idea: to move the search to a separate thread. Show search results in parts as they are ready, without waiting for the search to complete
Long calculations block the UI.
Tasks in a thread are executed sequentially. Until the current task is completed, the next task will not be executed. Processing mouse clicks, scrolling — these are also tasks.
Searching for 1,000,000 lines is long. While the search is in progress, the browser will not respond to user actions. The browser will “freeze”. To eliminate the freeze, you can move the search to a separate thread.
Simply putting the search in a separate thread is not enough. The UI will not freeze, but the search results will also take a long time to display. You need to show the results in parts as they are ready. Fast Data Grid displays the first 100 rows at once. While you scroll, Fast Data Grid searches for the next rows in a separate thread.
Data transfer between threads takes a long time. Only some types of data can be transferred by reference. Such types are called “Transferable objects”
Only the main browser thread has access to the DOM. This means that only the main thread can display the search results. Therefore, the results from the search thread must be passed to the main thread.
Data transfer between threads takes a long time, so transferring an array of grid rows with all the data for display is inefficient.
Only some types of data can be transferred efficiently. These types of data are called “Transferable objects”. Transferable objects are not cloned when transferred between threads, but are passed by reference. Uint32Array is a “Transferable object”. We can use Uint32Array to pass grid row indices.
setTimeout puts tasks at the end of the queue. With setTimeout, you can split a long operation into subtasks and not block the thread
Searching a million lines takes a long time. If the user changes the search string, you need to interrupt the previous search as soon as possible and start a new one. There is no way to interrupt the operation. But you can break the search into subtasks. Between subtasks, check whether the search string has changed.
setTimeout puts a task at the end of the queue. See Listing 1.
setTimeout(_ => console.log(1), 0);
console.log(2);
result
2
1
Listing 1. setTimeout puts tasks at the end of the queue. At first, the console will show 2, then 1.
Listing 2 searches over blocks of 5,000 lines. At the end of the block, setTimeout allows the thread to react to the change in newStr.
export const wait = () =>
new Promise(resolve => setTimeout(resolve, 0));
await arrForEachChunk(
data,
// chunkSize
5_000,
// forEachCallBack
(row, index) => { /* search */ },
// chunkEndCallBack
async () => {
await wait(); // let other tasks go
if (_str !== newStr) {
return false; // stop search
}
return true; // continue search
}
);
Listing 2. The search is performed in blocks of 5,000 lines. At the end of the block, setTimeout allows the thread to react to the change in newStr
arrForEachChunk is my utility, it is in the repository.
Self-promotion
I am making the most beautiful flowchart editor DGRM.net.
Check it out yourself.
Give it a star on GitHub.