Skip to content Skip to sidebar Skip to footer

Long Running Code Inside Html/javascript

I have a requirement to run an algorithm in a browser on button click. It very complex to code it in javascript and it would be very slow. Is there any recommended architecture for

Solution 1:

So, what are my next best options

Use a web worker (specification, MDN) so that the calculation isn't running on the main UI thread. The worker can even post updates to the main thread to show progress.


From your comment on the question:

It needs to be synchronous operation i.e on click...

That doesn't follow, and you really don't want it to be synchronous if it's doing heavy-lifting, you'll lock up the UI of the browser.

If you need to prevent further clicks while the processing is going on, just disable the button while it's running.

Here's an example which figures out 1 billion factorial (which takes a few moments even on modern browsers), with updates from the worker every million:

HTML:

<input type="button" class="the-btn" value="Click To Start">
<div>
    <div class="progress-wrapper">
        <div class="progress-bar"></div>
    </div>
    <div class="progress-counter">-</div>
</div>
<div class="result"></div>

CSS:

.progress-wrapper {
    border: 1px solid black;
    display: inline-block;
    width: 70%;
    height: 1em;
}
.progress-bar {
    display: inline-block;
    width: 0;
    background-color: blue;
    height: 1em;
}
.progress-counter {
    display: inline-block;
}

factorial_worker.js:

self.onmessage = function(e) {
    if (e.data && e.data.type === "start") {
        var n = 0, max = 1000000000, result = 0;
        while (n < max) {
            if (n % 1000000 === 0) {
                self.postMessage({type: "progress", progress: (n / max) * 100});
            }
            result += n;
            ++n;
        }
        self.postMessage({type: "done", result: result});
    }
};

Your main script in the page:

// Get the worker
var worker = new Worker("factorial_worker.js");

// Get our various elements
var btn = document.querySelector(".the-btn");
var progressBar = document.querySelector(".progress-bar");
var progressCounter = document.querySelector(".progress-counter");
var result = document.querySelector(".result");

function setProgress(progress) {
    var percent = progress.toFixed(2) + "%";
    console.log("Progress: " + percent);
    progressCounter.innerHTML = percent;
    progressBar.style.width = percent;
}

// Handle clicks
btn.addEventListener("click", function() {
    // Disable the button and tell the worker to get started
    worker.postMessage({type: "start"});
    result.innerHTML = "Working...";
    btn.disabled = true;
});

// Handle a message from the worker
worker.onmessage = function(e) {
    switch (e.data.type) {
        case "progress":
            setProgress(e.data.progress);
            break;
        case "done":
            // Re-enable the button
            btn.disabled = false;
            setProgress(100);
            result.innerHTML = "Result: " + e.data.result;
            break;
    }
};

Live Example (with a trick to embed the worker in the page, since we can't do external files on Stack Snippets):

// <ignore> Ignore this bit, it's just because we can't have a separate file in Stack Snippets
var blob = new Blob([
    document.querySelector(".the-worker").textContent
], { type: "text/javascript" });
// </ignore>

// Get the worker
// In your own code, you'd refer to a JavaScript file here:
// var worker = new Worker("my_worker_script.js");
var worker = new Worker(window.URL.createObjectURL(blob));

// Get our various elements
var btn = document.querySelector(".the-btn");
var progressBar = document.querySelector(".progress-bar");
var progressCounter = document.querySelector(".progress-counter");
var result = document.querySelector(".result");

function setProgress(progress) {
    var percent = progress.toFixed(2) + "%";
    progressCounter.innerHTML = percent;
    progressBar.style.width = percent;
}

// Handle clicks
btn.addEventListener("click", function() {
    // Disable the button and tell the worker to get started
    worker.postMessage({type: "start"});
    result.innerHTML = "Working...";
    btn.disabled = true;
});

// Handle a message from the worker
worker.onmessage = function(e) {
    switch (e.data.type) {
        case "progress":
            setProgress(e.data.progress);
            break;
        case "done":
            // Re-enable the button
            btn.disabled = false;
            setProgress(100);
            result.innerHTML = "Result: " + e.data.result;
            break;
    }
};
.progress-wrapper {
    border: 1px solid black;
    display: inline-block;
    width: 70%;
    height: 1em;
}
.progress-bar {
    display: inline-block;
    width: 0;
    background-color: blue;
    height: 1em;
}
.progress-counter {
    display: inline-block;
}
<input type="button" class="the-btn" value="Click To Start">
<div>
    <div class="progress-wrapper">
        <div class="progress-bar"></div>
    </div>
    <div class="progress-counter"></div>
</div>
<div class="result"></div>
<script class="the-worker" type="javascript/worker">
// This script won't be parsed by JS engines because its type is javascript/worker.
self.onmessage = function(e) {
    if (e.data && e.data.type === "start") {
        var n = 0, max = 1000000000, result = 0;
        while (n < max) {
            if (n % 1000000 === 0) {
                self.postMessage({type: "progress", progress: (n / max) * 100});
            }
            result += n;
            ++n;
        }
        self.postMessage({type: "done", result: result});
    }
};
</script>

Post a Comment for "Long Running Code Inside Html/javascript"