How to Outsource IndexedDB Transactions to Web Workers

Hi folks,

We have all encountered a moment in our career where we had this big process that slowed down our UI as if our computer was equipped with a GeForce 256 and 128mb of RAM ...

Thanks to Web Workers now we can keep our UI fluid and outsource calculations in the background using Pure JavaScript Files!

And that's what we are going to see today with a very simple example using IndexedDB as a bonus!

First of all the index.html, very simple here, two buttons, one to read all the db users and the other one to add a user.

< html > < body > Result: < div id = " result " > </ div > < button onclick = " readAll() " > read all </ button > < button onclick = " add() " > add </ button > </ body > < script src = " index.js " > </ script > </ html >

We then have the index.js, where we declare our Web Worker using the name of the JavaScript file that will contain the "interminable" process (in our case just adding and showing some users but in real life it gets a bit more complicated in general).

When we are done, the worker will receive a message and display the result on the page. Furthermore, the buttons will send messages to the worker. Which gives us this file:

w = new Worker ( "./handleDb.js" ) ; w . onmessage = function ( event ) { document . getElementById ( "result" ) . innerHTML = event . data ; } ; w . postMessage ( "init" ) ; function readAll ( ) { w . postMessage ( "readAll" ) ; } function add ( ) { w . postMessage ( "add" ) ; }

Finally inside the Web Worker:

let db ; let objectStore ; self . onmessage = function ( event ) { switch ( event . data ) { case "init" : { let req = indexedDB . open ( "vips" , 1 ) ; req . onupgradeneeded = function ( e ) { let db = e . target . result ; objectStore = db . createObjectStore ( "name" , { autoIncrement : true } ) ; self . postMessage ( "Successfully upgraded db" ) ; } ; req . onsuccess = function ( e ) { db = req . result ; } ; req . onerror = function ( e ) { self . postMessage ( "error" ) ; } ; } break ; case "readAll" : { readAll ( ) ; } break ; case "add" : { add ( ) ; } break ; } } ;

self here represents the worker. When it receives a message, we will use the data that we passed earlier to decide the action to trigger by a switch case (let's keep it simple).

readAll and add are self-explanatory.

Let's have a look at init.

We are opening the connection here with the db and preparing the callbacks.

If it's the first time, it's going to create an objectStore with keys that get autoIncremented.

After that, we are ready to go!

Let's have a look at the readAll function:

function readAll ( ) { let objectStore = db . transaction ( "name" ) . objectStore ( "name" ) ; let users = [ ] ; objectStore . openCursor ( ) . onsuccess = function ( event ) { let cursor = event . target . result ; if ( cursor ) { users . push ( cursor . value . name ) ; cursor . continue ( ) ; } else { self . postMessage ( "Every users: " + users . join ( ", " ) ) ; } } ; }

We are making a transaction on our name objectStore that was created earlier.

Creating a cursor and looping on it as long as there are elements, and we push the result in an array.

Once we are at the end of the loop, we are going to use self (our worker) in order to return a message with the users encountered.

This message will then be displayed on the DOM.

Finally the add function:

function add ( ) { let request = db . transaction ( [ "name" ] , "readwrite" ) . objectStore ( "name" ) . add ( { name : "User created just for the test" } ) ; request . onsuccess = function ( event ) { self . postMessage ( "Successfully added user in db" ) ; } ; request . onerror = function ( event ) { self . postMessage ( "something went wrong here" ) ; } ; }

This time, we make a transaction of type "readWrite" on our name objectStore and we add a user, generally in the real world, this is the place where the congestion point appears and 85% of the resources get busy with more complex insertions.

Once the insertion is done, we use our worker to send back a message that we collect with the onmessage callback.

And Voila!

You just mastered how to use Web Worker with IndexedDB!

Conclusion

Working with Web Workers is not that difficult. It's all about communication, you send a message to your worker and it sends the result when it's done. However, keep in mind that your worker doesn't have access to window, document, console, alert, etc. Finally keep in mind that your worker only lives as long as your user stay on the page, a refresh will restart them and kill all the processes. Otherwise, you can use server side workers like Sidekiq, Kue and Resque.