Keeping Your App Responsive – Part 3

You are reading the last part of a three-part series:

Part 3: The Solution

In part 1 and part 2, you have learned why your app can sometimes become unresponsive. The single-threaded nature of JavaScript means all of your app’s logic is run sequentially. All external events (like UI events) are first added to the internal Call Stack of the JavaScript engine and simply sit there until your other code is done executing.

When a user taps that special button in your app, it should open the associated window immediately. But if the application is still busy processing other stuff that simply won’t happen. Having only that single thread at your disposal, there is really nothing you can do to change this behavior. However, now that you understand how (part of) JavaScript’s internal processing works, you can optimize your code to allow external events to be processed significantly faster than before. You might not be able to beat the system, but you can certainly tweak your code to (partially) overcome its limitations!

Ladies First

Please consider the following statement:

setTimeout(foo, 0);

This might read like a hack and it could certainly be considered so. You are asking the JS engine to execute foo after 0 milliseconds, or in other words: immediately. Originally the setTimeout-function was not intended to be invoked this way. In fact, it took some time before web browsers stopped choking on a line like this.

So what does it do? On a first glance you might think it doesn’t do anything. The function foo will be executed right away since it isn’t actually being delayed.

But since you have read part 2 of this article, you know better. This awkward looking statement could potentially change a lot! Instead of executing foo right away, it is instead only added to JavaScript’s internal Call Stack right away. This happens when the timeout has passed, but since the provided timeout equals 0 ms there is no delay.

Now the code-block wrapping this setTimeout statement would normally continue execution until it’s done. Afterwards, if the stack is empty, then foo would indeed be executed right away. If it’s not, then the items preceding foo on the stack will be executed first.

Effectively you could read setTimout(foo, 0) as ladiesFirst(foo). The code enveloping the setTimeout statement would happily continue, but foo -a real gentleman- walks all the way to the back of the line. Everyone that was previously behind him (actually not just the ladies) is now in front (on the Call Stack), and thus will be processed before foo ever will. The reason for this is because the stack is always handled FIFO (first-in, first-out). The others were already waiting, while foo only joins in now.

If the setTimeout statement marked the end of the current code block being executed, this allows the JS engine to execute all events that have accumulated while it was busy. Afterwards you simply carry on with foo.

Defer

Since the term ladiesFirst is a bit vague, not very descriptive (without the proper context) and -most of all- discriminative, the various JavaScript libraries that offer this functionality opted for a more serious name. For example, the creators of Underscore came up with defer. An additional benefit of using Underscore’s defer is that it allows you to also pass arguments to the function being deferred.

If you are using Alloy then Underscore is already at your disposal. If not then you could borrow its defer implementation, or simply wrap setTimeout(..., 0) yourself.

Making the best use of your single thread

Using the mechanism described above will allow you to greatly improve the responsiveness of your app. Unfortunately this will require you to restructure your code significantly.

A good practice in programming is to split large function into several smaller ones. Hopefully you also follow this guideline while writing code, but it’s all the more important that you do this from now on. If a any function does multiple things then split it into several smaller functions, each handling a single aspect.

Something you might not already be doing is isolating the code within your loops into separate functions. If you are used to something like foreach then you do this already. Underscore comes with several functions that iterate over an object or array using a callback function as well.

Now your logic’s been broken down into small(er) function, let’s see where defer comes in next!

Defer To the Rescue!

Having all these small function and knowing about defer, your first thought might be to wrap most (or all) of them in defer. Please don’t! It would indeed be fairly simple to update your application’s logic that way, but the flip-side is that it won’t work at all.

By using defer at strategic locations, you can really increase the responsiveness of your app. However, special care needs to be taken when wrapping function calls in defer. Doing it at the wrong places might not help at all, while burdening the Call Stack unnecessarily.

When using defer to increase the responsiveness of your app, there is one important principle to always keep in mind:

Only one deferred function should be on the Call Stack at all times

Getting it right

If there are several deferred functions on the Call Stack, then user-interaction will be delayed until all of these functions have been executed. This isn’t helping, since this is basically how your app already functioned without defer. What you instead want to achieve is that user-interaction will be handled in between every two deferred functions.

In order to understand why this is and how to do this, let’s look at two examples.

Example 1: A simple function

Let’s assume a window was opened and the app needs to initialize the window’s contents afterwards. The initWindow function takes care of this. During initialization two unrelated tasks need to be performed and both these code blocks have been isolated into their own function, named heavyTask1 and heavyTask2. Both of these functions are considered to perform some hefty processing.

This is what the code might look like:

function initWindow() {
    heavyTask1();
    heavyTask2();
}

Without using defer heavyTask1 and heavyTask2 would be run sequentially. If a user taps a button while heavyTask1 is running, we can guarantee that this event won’t be processed before heavyTask2 is done executing.

By using defer on heavyTask2 this behaviour will be changed.

function initWindow() {
    heavyTask1();
    _.defer(heavyTask2);
}

In the updates code sample the user’s tap-event will be executed before heavyTask2 will execute. If the user didn’t do anything and there were no events on the Call Stack when heavyTask1 finished its execution, then heavyTask2 will execute after initWindow (and any function that invoked it) is done executing.

So this is a good use.

Stepping Things Up

Let’s look at a variant of the code above:

function initWindow() {
    heavyTask1();
    _.defer(heavyTask2);
    _.defer(heavyTask3);
}

On a first glance, this looks like a good solution as well. If the user taps a button while heavyTask1 is running, it will be resolved before both heavyTask2 and heavyTask3 will execute. So far, so good!

But if a user taps a button while heavyTask2 is executing, what then? In that case it won’t be resolved until after heavyTask3. This is because heavyTask3 was already on the Call Stack when heavyTask2 started its execution. The button tap will then simply be added after that.

Doing it like this violates the key principle outlined above. Running this piece of code will result in multiple (two) deferred functions on the Call Stack.

Then how would you do this?

Deferring Done Right

Getting the full benefit out of defer requires us to change our code more radically. Simply wrapping heavyTask3 within defer is not going to do the trick, instead the heavyTask2 function should be made responsible for deferring the next part of the code execution. Below is our updated code sample:

function initWindow() {
    heavyTask1();
    _.defer(heavyTask2);
}

function heavyTask2() {
    // Do some heavy processing
    ...
    _.defer(heavyTask3);
}

In this case heavyTask3 will only be added to the Call Stack when heavyTask2 is nearly done executing. When heavyTask2 is executing it is no longer on the Call Stack, which implies that when heavyTask3 is added, there is only one deferred function on the stack. This is in accordance with the principle outline earlier. As a result any user event that occurs while heavyTask2 is executing will be added to the Call Stack before heavyTask3 is added. So a button tap is now handled before heavyTask3 executes.

Let’s look at iterations next!

Example 2: An iteration

This second example is a variant of the first one. The heavyTask function was changed to include an iteration. It loops through a big array using a callback function called smallTask.

function initWindow() {
    heavyTask();
    ....
}

function heavyTask() {
    ...
    _.each(bigArray, smallTask);
    ...
}

function smallTask(item) {
    // Do something to item
}

Note that the Underscore function each is used here, since it simplifies the code significantly.

Our goal is to defer the individual iterations so that a button tap after the (for example) 324th iteration will be handled before the 325th. Let’s update smallTask to get this done.

function smallTask(item) {
    _.defer(smallTaskInner, item);
}

function smallTaskInner(item) {
    // Do something to item
}

The smallTask function has been turned into a wrapper. Its internals were moved into a new function called smallTaskInner that it now defers. As before, for every element of the bigArray smallTask is executed. But the smallTaskInner invocations will not, until the initWindow process finishes.

Each time smallTask is invoked an execution of smallTaskInner is added to the Call Stack. Since heavyTask is still in control, it will keep invoking smallTask continuously until bigArray’s end has been reached.

If a user taps a button while heavyTask is iterating over bigArray, then that event will be added to the Call Stack somewhere in between the many smallTaskInner executions. If the button is tapped after heavyTask has finished, the user will still have to wait until all smallTaskInner calls on the stack have been executed. In other words: all those executions will still block the app.

The end result is that the iteration performed by heavyTask has been greatly sped up, since it is not processing any of the items. As a result however, a very large number of pending executions have been added to the Call Stack. After heavyTask wraps us, all these items will be executed one after another until the last one finishes. This offers you no benefit at all, while it does increase the size of the Call Stack to (potentially) enormous proportions.

Clearly this code sample severely violates the core principle mentioned earlier. And as a result it doesn’t help your application become more responsive.

So, what is the correct solution?

Iterations Done Right

Just as with the previous example more structural changes will need to be made in order to get this right. In this case the iteration itself will have to be redone completely. To facilitate this, heavyTask‘s iteration has been moved into a separate function named heavyIteration. Instead of using Underscore’s each-function the iteration is now handled using recursion.

Below is our updated sample:

function heavyTask() {
    ...
    heavyIteration();
    ...
}

function heavyIteration(i) {
    i || (i = 0); // If called directly then i is undefined, so make it 0

    smallTask(bigArray[i]);

    if (++i < bigArray.length) {
        _.defer(heavyIteration, i);
    }       
}

function smallTask(item) {
    // Do something to item
}

For those unaccustomed to recursion this might look a bit weird. After all, heavyIteration is invoking itself. But as the value of i will increase with every call, a point will be reached where it matches bigArray’s size and the recursion will stop.

Doing iterations like this makes sure there is only one deferred function on the stack at all times. As a result all user interaction that occurs during this loop will be handled immediately, before the iteration is allowed to continue.

Pitfalls

In general it’s pretty safe to use defer here and there to give your app some air during operations that block the UI for a significant amount of time. Obviously you’ll gain the most if you apply this technique throughout your application from the get-go. But in that case you’ll also have to deal with pitfalls that did not exist before.

Take for instance the scenario where a user taps a button that opens a new window, while the current window is still being initialized. That new window will obviously need to be initialized as well. Since both initializations make extensive use of defer, you’ll end up with both initialization processes running “at the same time”. This slows down the initialization of the window that was just opened and currently holds the focus. Furthermore the original window might not even matter anymore, so perhaps its initialization should actually just stop or be put on hold.

Conclusion

If you want to keep your application responsive, you’ll have to understand what is going on beneath the hood. More importantly, you will need to rethink your code completely.

I hope this article (all three parts) has enabled you to improve the responsiveness of your application. At the very least I hope it has increased your insight into why your app seems to block user input from time to time.

More examples

If you find this whole matter still a bit complex, then be sure to hit Github and check out the DeferTests application. This app serves purely to demonstrate the user of defer and contains several variants of the examples mentioned above. It allows you to experience how they work in your simulator or on your phone, before actually changing your own application’s code.

The DeferTests app also contains a very basic ProcessManager that shows how you could deal with some of the the pitfalls mentioned in the previous paragraph.

Last words

It’s hard to explain all of this without actually running code samples that show what’s going on. I hope the DeferTests app on Github will help to clarify any questions you might still have. If not then please leave a comment, or contact me on Twitter (@ronaldtreur).

If this topic is simply too much, come visit my talk on this subject at the European Titanium Conference in Amsterdam, June 28th 2014. I will discuss all of the above using live demos that might make all of this more tangible for you.

Ronald Treur is a freelance developer and the co-founder of Snowciety, a multi-platform mobile app created using Titanium & Alloy. He has been developing websites since the age of 11 (starting out on paper due to lack of a real computer) and has been working on mobile apps since Titanium 1.5. For development he has experience with Javascript, PHP and Java and to a lesser extent C and C++. For webdesign he relies on his knowledge of HTML5 and CSS3. Other interests include game-design, movies and talking at meetups (recent addition!).


Comments