Article

This is in continuation to JavaScript runtime: Call Stack and Event Queue - Part 4

<!DOCTYPE html>
<html>
  <head>
    <title>Stack-Queue</title>
    <link href="assets/css/bootstrap.css" rel="stylesheet" />
    <style>
      .backColor {
          background-color: wheat
      }
    </style>
  </head>
  <body>

    <div class="container-fluid">
      <h2 style="margin-bottom: 20px;">Demo: Call Stack and Event Queue</h2>
      <div class="row" style="margin-bottom: 20px;">
        <div class="col-sm-9">
          <input placeholder="Try to enter text immediately after you click the button. Press and hold a key until it fills the input to test the reactiveness."
                  type="text"
                  class="form-control input-lg">
          </input>
        </div>
      </div>
      <div class="row" style="margin-bottom: 20px;">
        <div class="col-sm-6">
          <button id="btn"
                  class="btn btn-lg backColor">
              Generate List Items
          </button>
        </div>
      </div>
    </div>

    <script>

      var button = document.getElementById('btn');

      //the body element to which we append "ul" (unordered HTML list) items
      var body = document.getElementsByTagName('body')[0];

      button.addEventListener('click', function () {

        //The classList property returns the class name(s) of an element. This property can be used to add, remove and toggle CSS classes on the element. If backColor property is already present, toggle() will remove it. If it is not there, toggle() will add it. 
        button.classList.toggle('backColor');

        //When you uncomment one among the following 3 lines, make sure that you comment out the other 2 lines
        createListItems();
        //setTimeout(createListItems, 0);
        //createListItemsInChunks();
      });

      var createListItems = function() {
        for (i = 1; i <= 150000; i++) {
          newUl = document.createElement('ul');
          newUl.textContent = 'Element: ' + i;
          body.appendChild(newUl);
        }
      }

      var createListItemsInChunks = function() {
        for (var i = 1; i <= 150; i++) {
          (function (currentIteration) {
            setTimeout(function() {
              var numUlsToAddInOneIteration = 1000;
              for (var count = 1; count <= numUlsToAddInOneIteration; count++) {
                newUl = document.createElement('ul');
                console.log(currentIteration);
                newUl.textContent = 'Element: ' + (((currentIteration -1 ) * numUlsToAddInOneIteration) + count);
                body.appendChild(newUl);
              }
            }, 0);
          })(i)
        }
      }
    </script>
  </body>
</html>

This code simply demonstrates the main components of the JavaScript runtime, namely, Call Stack and Event Queue.

In the first run, uncomment out this line:

createListItems();

When the button is clicked, the runtime does the following: 1. It pushes the createListItems() onto the call stack. This function is a time consuming operation which adds 150,000 list items to the body element of the document. 2. It creates an event and places it in the event queue whose task is to re-render the change made to the button's background color. The event keeps waiting for its turn until the stack becomes empty.

While the createListItems() is executing, the stack is occupied and event callback placed in the event queue is waiting for its turn. After all the list items are generated, the stack becomes empty and the event loop can now fetch the message from the event queue and places it on the stack. Therefore, if you look at the result, a change in the button's color is rendered only after the createListItems() finishes its execution. Also, if you try to write something in the input box, the text will be rendered only after createListItems() completes making the browser unresponsive for the duration of the code being executed on the stack.

If you want to write the non-blocking code, you must add it to the event queue. In the second run, uncomment out this line:

setTimeout(createListItems, 0);

This means that instead of just invoking the createListItems() directly, it is passed as a callback to the setTimeout() with a time delay of 0 seconds. Even though the setTimeout duration is 0 milliseconds, the createListItems will be placed in the waiting queue, instead of the stack, and executed at a later time. A delay of 0 ms means that you are forcing a function call to be asynchronous, but without any extra delay.

Now, if you look at the result, the button's color is rendered immediately after you click the button and then the list items are displayed later. If you try to write something in the input box, you will still observe the browser's unresponsiveness because the createListItems() will run at least for couple of seconds freezing it for that period of time.

Since the createListItems() is a time-taking operation, it takes a few seconds to finish its execution. The browser becomes unresponsive for the duration of time its code being executed irrespective of whatever strategy you choose from among the two described above. Since JavaScript is single threaded, you will only ever be in one function at any point in time. Therefore, the createListItems() function will be executed completely before any other one is entered.

In the third run, uncomment out this line:

createListItemsInChunks();

Note that setTimeout() is simply a function that enqueues a function call to be executed at a later time. In itself, it cannot make the browser responsive. If you want that the user's browser should not freeze, you'll have to divide your large task into small ones, each taking little time, and then calling each of these tasks asynchronously. For example, instead of adding 150,000 list items in one single step, you can iterate 150 times and in each iteration, use the setTimeout() to add 1,000 items.

This leads to 150 events lined up in the event queue. Keep the console log open when the code is run. Observe the display of the values of the current iteration variable in the console. Notice the time delay between two displays. Immediately after clicking the button, start entering the text in the input box and you will observe the browser is now responsive to a great extent. It still freezes but only for a very small amount of time.