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>
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 pletion?
Share Improve this question edited Sep 27, 2016 at 20:28 Travis J 82.4k42 gold badges212 silver badges279 bronze badges asked Sep 23, 2016 at 17:26 bongbangbongbang 1,7124 gold badges19 silver badges36 bronze badges 8-
2
If your
newContent
is being prepared by some synchronous methods and you are certain about the availability of the content aftertimeout
, there is no harm doing it this way.. – Rayon Commented Sep 23, 2016 at 17:31 - 2 Find out the time. Generate the content. Find out the time again. If less than 2 seconds have passed, wait the remainder of the time and then replace. Otherwise, replace the content immediately. – Heretic Monkey Commented Sep 23, 2016 at 17:32
- Looks reasonable to me. Maybe incorporate fade in/fade out so the change of content is not so subtle – Kevin Le - Khnle Commented Sep 23, 2016 at 17:32
-
1000 factorial is way past the ability of JS to represent numbers. Anyway, "whether this is advisable" is a user experience question, not a programming question, unless you are asking if your particular implementation is an advisable approach. And why would you ever not need to worry about
newContent
not being ready? – user663031 Commented Sep 26, 2016 at 18:35 - 1 Your language is not hypothetical. It's called JavaScript, and the language mechanism to acplish what you want is called "promises". But you have to invoke them; they do not happen magically, not do they make something finish before it does, nor alter the immutable fact that if you want something to be done or ready, then it has to be done or ready. – user663031 Commented Sep 27, 2016 at 5:32
2 Answers
Reset to default 9Your long-running putation 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 plete 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.
Using async functions
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 putation.
await timeout(ms); // Wait for two seconds.
output(await result); // Wait for putation to finish and output.
}
You have two requirements:
- Do not hide the introduction until at least 2 seconds have passed.
- Do not hide the introduction until the content is ready.
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);