Reputation: 1712
In the minimal example below, the replacement of old content is deferred by setTimeout
to give the user time to finish viewing it. In the meantime, new content is being prepared so as to avoid blocking the user interface during a potentially expensive task.
var div = document.getElementById('wrapper');
var newContent = document.createElement('ul');
setTimeout(function() {
var header = div.firstElementChild;
header.innerHTML = 'New Content';
header.nextElementSibling.remove();
div.appendChild(newContent);
}, 2000);
// Make new content while we wait
[1, 10, 100, 1000].forEach(function(x) {
var li = document.createElement('li');
li.innerHTML = 'Factorial of ' + x + ' is ' + factorial(x);
newContent.appendChild(li);
});
function factorial(num) {
if (num === 0) {
return 1;
} else {
return (num * factorial(num - 1));
}
}
<div id='wrapper'>
<h1>Old content</h1>
<p>Read it before it's gone.</p>
</div>
My concern with this approach is that it does not seem to handle newContent
not being ready when the replacement is due to take place. I am also uncertain if this approach will block the user interface or if the task used by setTimeout
will be executed concurrently.
How can I ensure that the user interface is not blocked while executing a potentially expensive task and immediately using it upon completion?
Upvotes: 7
Views: 465
Reputation:
Your long-running computation is going to block the browser, which is never a good idea. Therefore, you should put it in a web worker.
These days it is better practice to write asynchronous code with asynchronous tools such as promises. Here's a general, pseudo-code level approach:
// Create a promise which fulfills after some # of ms.
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Create the worker, kick it off, and
// return a promise which fulfills when the worker reports a result.
function waitWorker() {
const worker = new Worker('factorial.js');
worker.postMessage([1, 10, 100, 1000]);
return new Promise(resolve =>
worker.addEventListener('message', event => resolve(event.data))
);
}
// Wait for both the worker to complete and the two seconds to elapse.
// Then output the data.
Promise.all([timeout(2000), waitWorker()])
.then(values => output(values[1]);
Writing the worker is left as an exercise.
You could also express this a bit more cleanly using async functions, if your environment supports it, as follows:
async function calcAndWait() {
const result = waitWorker(); // Kick off computation.
await timeout(ms); // Wait for two seconds.
output(await result); // Wait for computation to finish and output.
}
Upvotes: 9
Reputation: 810
You have two requirements:
The changes below satisfy that.
<html>
<body>
<div id='wrapper'>
<h1>Old content</h1>
<p>Read it before it's gone.</p>
</div>
<script>
var div = document.getElementById('wrapper');
var newContent = document.createElement('ul');
var contentReady = false;
var timesUp = false;
function onContentReady() {
if (! timesUp || ! contentReady) return;
var header = div.firstElementChild;
header.innerHTML = 'New Content';
header.nextElementSibling.remove();
div.appendChild(newContent);
}
setTimeout(function() {
timesUp = true;
onContentReady();
} , 2000);
function makeContent() {
// Make new content while we wait
[1, 10, 100, 1000].forEach(function(x) {
var li = document.createElement('li');
li.innerHTML = 'Factorial of ' + x + ' is ' + factorial(x);
newContent.appendChild(li);
});
contentReady = true;
onContentReady();
}
function factorial(num) {
if (num === 0) {
return 1;
} else {
return (num * factorial(num - 1));
}
}
setTimeout(function() {
makeContent();
} , 4000);
</script>
</body>
</html>
Change the time value in this code to be less than 2 seconds and more than two seconds to see that.
setTimeout(function() {
makeContent();
} , 4000);
Upvotes: 4