In user interface programming, most platforms have a concept of a main UI thread, and at least one background thread. This provides the ability to perform long running tasks in the background without preventing the user from interacting with your application.
Web pages are different from other platforms since javascript is a traditionally single threaded language. Instead, blocking operations are performed asynchronously, and their results are typically deferred to another function for later processing. However, excluding I/O operations, this still technically limits a web page to performing one task at a time and could potentially block user interaction if run long enough.
The solution (tl;dr)
There are two ways to get around this problem in javascript. If your task is flexible enough to divide into smaller subtasks, you can use the setTimeOut() function recursively to allow the browser to perform other tasks before moving on to its next subtask. However, many modern browsers also allow the web page to effectively create background threads through the use of Worker() api. Although web workers can be simpler and yield greater performance, they aren’t compatible with older browsers.
Example – Calculating factorial
The following code illustrates two examples of calculating the factorial of a number without blocking the UI thread. It also illustrates how much longer a function may take to run if it’s split into subtasks with setTimeout. You can also view a demo of this code running in action.
factorial.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
/** * Calculates n factorial by pairing a recursive function with * setTimeout in order to prevent UI blocking * @param {Integer} n - The number you want to calculate factorial for * @param {Function} callback - A callback function that takes the result as an argument * @example * factorial_recursive(10000,function(result){ console.log(result)}); */ function factorial_recursive(n,callback,result){ if(!result){ result = 1; } if(n === 0){ callback(result); } else{ var result = n * result; setTimeout(function(){ factorial_recursive(n - 1,callback,result) },0); } } /** * Calculates n factorial iteratively when used with the web worker api * @param {Integer} n - The number you want to calculate factorial for * @example * var myWorker = new Worker("factorial.js"); * myWorker.onmessage = function (oEvent) { * console.log("Worker said : " + oEvent.data); * }; * myWorker.postMessage(100000); * factorial_recursive(100000,function(result){ console.log(result)}); */ function factorial_iterative(n){ var val = 1; for(var i = 2; i <= n; i++ ){ val = i * val; } return val; } onmessage = function (oEvent) { postMessage(factorial_iterative(oEvent.data)); }; |
factorial.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<html> <head> <script src="factorial.js"> </script> </head> <body> Demo code taken from the post "<a href="http://akeem.mclennon.com/2014/07/separating-the-javascript-ui-thread">Separating the Javascript UI Thread to create more responsive web apps</a> <br /> This interface will not be blocked while factorial is being calculated <select><option>1</option><option>2</option></select> <script type="text/javascript"> var myWorker = new Worker("factorial.js"); myWorker.onmessage = function (oEvent) { alert("Worker said : " + oEvent.data); }; myWorker.postMessage(1000000000); factorial_recursive(1000,function(result){ alert("factorial_recursive :" + result)}); alert("This message will not be blocked by previous code"); </script> </script> </body> </html> |