[tech] Javascript Promises - Breaking Ui Code Into Async Blocks And Avoid Freezing Ui

This is a technical article on how Acousterr's tab parser utilizes Javascript Promises to avoid freezing UI while parsing is in progress

Background :

JavaScript is single threaded, meaning that two bits of script cannot run at the same time; they have to run one after another. In browsers, JavaScript shares a thread with a load of other stuff that differs from browser to browser. But typically JavaScript is in the same queue as painting, updating styles, and handling utilizer actions (such as highlighting text and interacting with form controls). Activity in one of these things delays the others.

Whenever there is a long running loop in javascript, it freezes the UI as all other actions are halted till the time And you might see a dialog like this :



The Problem Statement

Acousterr allows anyone to paste any tab from internet and parses that to enable playback properly. Now that parsing step utilizes several iterations to adjust note positions to begin on correct locations. In version 1 of its implementation , acousterr utilized multiple long running loops sequentially, to achieve various tasks for formatting. While this approach did not cause any trouble for relatively short tabs (5-10 staves), but for longer tabs (> 10 staves), there was a visible freezing of the browser window. Some times , a browser kill would be needed, which was very annoying and made that feature unusable to an extend.


The Solution - Breaking code into smaller recursive blocks and asynchronously executing them

So instead of a long running loop, what we did was to break the task into a small recursive blocks. Every recursive block was responsible of reformatting one unit in a stave at a time and recursively called this block again if more formatting was needed. Now that the task is recursive,we can execute each task with window.setTimeout, with 0 time interval, which tells the browser to execute the task in next available cycle, thereby un-blocking the code.

function doTaskAsync() {
window.setTimeout(function(){
doTask();
},0);//causes execution to break and pass doTask() to next available cycle
}

function doTask() {
//bla bla bla
//...
//...

//recursive call to current function, but asynchronously
doTaskAsync();
}

In the above code snippet, doTask() implements a relatively small task , which is executed faster. doTask() would be called recursively, till some exit condition is met , till the complete original task is finished.

This way, instead of 1 long running and blocking loop, the code is broken down into multiple smaller code blocks which are interleaved with other CPU cycles, causing a smooth UI experience and NO freezing and unresponsiveness.

But wait - how do we know if all the async tasks which we started are complete?

So far, we have explained how to break and execute tasks asynchronously, but in many cases, we might need to know when all these tasks get completed. For example in our case, we display a message  something like "The tab has been formatted, now you can play it".


One way to to keep a global variable and maintain which all tasks were started and keep striking off those tasks from that variable as and when they complete. Thats a very cumbersome way, and a neater solution is possible with Javascript promises. Also it might be a tricky thing to do when tasks are dynamically added to your list.

What are Javascript Promises

A promise is an object that may produce a single value some time in the future: either a resolved value, or a reason that it’s not resolved (e.g., a network error occurred). A promise may be in one of 3 possible states: fulfilled, rejected, or pending. Promise users can attach callbacks to handle the fulfilled value or the reason for rejection. Promises are eager, meaning that a promise will start doing whatever task you give it as soon as the promise constructor is invoked. If you need lazy, check out observables or tasks.

The promise constructor takes one argument, a callback with two parameters, resolve and reject. Do something within the callback, perhaps async, then call resolve if everything worked, otherwise call reject.


function doTaskAsync() {
var promise = new Promise(function (resolve, reject) {
window.setTimeout(function () {
doTask();
resolve(); //manually call the resolve function when this task completes
}, 0);//causes execution to break and pass doTask() to next available cycle
};
_formattingTaskPromises.push(promise); // some variable to store all promises
}

function doTask() {
//bla bla bla
//...
//...

//recursive call to current function, but asynchronously
doTaskAsync();
}

//listener to fire when all promises have been resolved
Promise.all(_formattingTaskPromises).then(function() {
//yayy all promises have been resolved, task completed
console.log("Your tab is ready to play");
});

Now we have wrapped our async code into a Promise, and that promise will come to resolved state only when we call resolve() Promise.all provides an easy way to call a piece of code when all the supplied promises have been resolved.

Want to see tab parsing in action? check out this video


tabs and chords, guitar tabs, music transcription, music technology, deep learning, music information retrieval, transcribe into tabs & chords, chordify, music renditions, songsterr, reverbnation, tabulature, bollywood chords, ultimate guitar, yousician, guitar lessons, acoustic guitar, electric guitar, guitar tuner, 911 tabs