Making do with javascript’s single threaded nature

Unfortunate situation where web workers are not an option

The Good

Javascript is slow. Javascript is easy. Javascript is weird. Javascript is problematic. And people seem to love it! We are talking about the world’s most popular programming language in the OSS community.

As I’m writing this, Javascript is responsible for 80% of applications I use on a daily basis. I’m not happy about it, but its the way it is. Even my code editor and music player rely on it.

The bad

Back to the problematic thing. Its single threaded. What it means is it can only do one thing at a time. If your browser’s engine is doing something which is bound to take a long time, your website will practically be frozen. Take this for example:

let arr = new Array(50000).fill(1);

arr = arr.map(el => Math.round(Math.random() * 100));

for(let i = 0; i < arr.length; i++) {

console.log(arr[i]);

}

Open your dev console, and try it in another window. It will block it. Depending on your processor it could take less or more. But it should block your window. It may even block your browser. We don’t want that.

A client of the company I work at, requested a mobile application that primarily works offline, but once online will sync with the server database. So we got two databases. And the syncing happens in an interval of 5 minutes (When there’s an internet connection available). The application uses SQLite and TypeORM to define entities, connect to database, do SQL queries and stuff.

The Ugly

Long story short, we had a problem trying to sync 100 objects into the local database. Because these objects were complex and had many related tables, inserting them into the offline database was taking ~10 seconds for each batch. Mind you this was only one batch, and we had cases where there were 10K such records we had to sync.

While it also might be unoptimized database queries at fault here, as a frontend developer I had to mitigate the issue.



What do we know:

We are trying to save 100 objects using the functions the backend team has provided (TypeORM exposes functions returning promise object) using Promise.all

This is blocking everything while the insertions happen

Project manager is not very delighted

Ideas:

Plan A: Web workers. It seems fitting for this kind of thing.

Web workers. It seems fitting for this kind of thing. Plan B: Show a loader while the syncing happens.

Show a loader while the syncing happens. Plan C: We are using Ionic so we might be able to sneak in a nodejs server. Crazy eh.

Plan A: Web Wokers

Different thread?! Awesome! Okay this should be it. Phew 😅. Close one.

* 10 mins later

I can’t pass objects by reference or use services from the place I’m calling the function?

Ok I’ll just initialize and use the database on the worker thread, and using the appropriate protocols for communicating with the main thread.

* 15 mins of chat with the backend dev later

We need cordova plugins (cordova-sqlite-storage) working here. Turns out that’s not possible. Cordova only provides API-s for the main window and doesn’t care about any other web worker we might be running.

Now we in deep 💩.

Lets try Plan B (And C 🤦🏻‍♂️).

Project Manager: Plan B is not ideal solution. Can you come up with something else?

My brain:

Don’t say it

Don’t say it

Don’t say it

Don’t say it

Don’t say it

Don’t say it

Don’t say it

Don’t say it

Me:

“We can initialize and run nodejs server on the phone and expose endpoints using express, which would offer a way to communicate with the local database. It would also take care of syncing with the API too.”

Everyone was looking at each other laughing and wondering who my dope dealer is. 🧐

But yeah, I showed those guys real good. I got nodejs working on iOS doing stuff in another thread. That was incredible. Kudos to @janeasystems. You can check out the project here.

This is basically what I’m going to be doing the next 2–3 months. The possibilities are endless. But more on that later.

Hold Up

Everybody was happy. We had the database running and everything working, but once we were hitting the endpoint for saving 100 complex objects a time, we couldn’t call any other endpoint until those were done. 😰 NodeJS uses V8 (or chakra in our case), which is the same technology used on the browser really. So we were hitting the same problem here. The call stack was getting blocked.

Now what? Lets dig deep 👨🏻‍🔬.

It didn’t take long for me to land into this godsend video by @PhilipRoberts (I love you Phil).

So what is basically happening the callback that kicks in our process of inserting/updating things on the database is taking a long time, thus blocking the call stack. Fair enough. So what can we do here?

Remember: we still must return a promise to let the caller know when it has actually completed insertion, while still maintaining a non-blocking asynchronous execution method. This is existing code which doesn’t just do the insertions. The code we are going to tamper with exists in a then handler, and there are other then handlers after this one is completed.

Introducing: setTimeout ⏳

This is a web API which takes two parameters. The first one is the callback function which is to be executed when the second parameter countdown finishes. So it looks like this:

setTimeout(() => {

console.log(‘Imma say hi in a sec’);

}, 1000);

The countdown happens on a different thread, so the Call Stack is not blocked. When the countdown finishes, the callback function is put on the Callback Queue, where the Event Loop will take care of putting it on the Call Stack once it is free.

We can’t use Promise.all as it still fills the CallBack Queue with all our promises, so they still occupy a big part of it, which are going to start being put on the Call Stack eventually.

Simple enough, lets create a function which wraps the promise execution in a setTimeout and calls itself after each is saved until the last one. Its not that simple actually. Took me couple tries.

private addPosts(posts): Promise < any > {

const repo = getRepository('posts') as Repository < Post > ;

const insertPosts = (postNumber) => {

setTimeout(() => {

repo.savePosts(posts[postNumber]).then(() => {

if (postNumber < posts.length - 1) {

insertPosts(postNumber + 1);

}

});

});

}

insertPosts(0); // kick off the insertion

}

Our function is supposed to return a promise, and we most certainly can’t return something which lives inside the setTimeout. Our current code actually doesn’t return anything other than undefined (that’s implicit).

Last Step: Return what we promised.

private addPosts(posts): Promise < any > {

const repo = getRepository('posts') as Repository < Post > ;

return new Promise((resolve, reject) => {

const insertPosts = (postNumber) => {

setTimeout(() => {

repo.savePosts(posts[postNumber]).then(() => {

if (postNumber < posts.length - 1) {

insertPosts(postNumber + 1);

} else {

resolve()

}

});

});

}

insertPosts(0); // kick off the insertion

})

}

This free’d up our Call Stack and the NodeJS endpoints are working in parallel now, while having the flow maintained. Best thing: It is also working on the non-NodeJS approach. That means less things to worry about, and happier project manager.

RxJS alternative⛲️

RxJS from what I understand is a library which makes it a breeze to control the flow of your application. It has tons of use cases, but what I have been using it most for is the up mentioned.

Two pillars of RxJS are the Observer and the Observable object. The Observable is an object you can subscribe to from anywhere in your application. It can emit data not only once, but as many times as its observer sees fit. So an Observer is an object which produces the data that the subscribers of the Observable can consume. My understanding of RxJS is still in its infancy, so I might have gotten some things wrong here. I’m not going to give examples, as there are tons of them provided by the one and only @BenLesh on his Medium profile.

For now lets see how we can use the Subject object (which acts both as an observer and observable), to control the flow of our async non-blocking insertion function, while still returning a promise which completes when the insertion is done.

private addPosts(posts): Promise < any > {

const repo = getRepository('posts') as Repository < Post >;

const insertionLifecycle$ = new Subject<any>();

const insertPosts = (postNumber) => {

setTimeout(() => {

repo.savePosts(posts[postNumber]).then(() => {

if (postNumber < posts.length - 1) {

insertPosts(postNumber + 1);

} else {

insertionLifecycle$.complete();

}

});

});

}

insertPosts(0); // kick off the insertion

return insertionLifecycle$.asObservable().toPromise();

}

I think the code is self explanatory. We are solving our problem by returning a promise which under the hood is a Subject (cold observable), which completes(resolves) when there’s no more posts to save.

Conclusion: Even though the web has come very far to providing great tools for dealing with Javascript’s letdowns, there are still real life scenarios where its essential to know the behind the scenes of how the browser and its JS implementation works, to end up glorious on solving the thread blocking problems one might have.