2

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 it ? Ideally I would like to code it in C++ or Python , but i guess it is not possible to run it inside a browser on button click. So, what are my next best options ?

I can't have it run on a serverside because there will be 1000s of clicks on the page which will result in too much communication back and forth.

7
  • 1
    developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/… Commented Sep 21, 2017 at 14:16
  • 1
    There's something calles WebAssembly (developer.mozilla.org/en-US/docs/WebAssembly) you may wanto keep an eye on that. Commented Sep 21, 2017 at 14:17
  • @meta: Big time. It's going to be huge when it finally breaks out. Commented Sep 21, 2017 at 14:19
  • um, Python tends to be slower than JavaScript, mostly due to lack of JIT compilation. Commented Sep 21, 2017 at 14:22
  • Thanks summet, meta and T.J. I will look into those options. It needs to be synchronous operation i.e on click , i cant have it running on a separate threat. Is there any way to run an optimized binary code on a button click ? An interpretative language like javascript will be slow for use in analytic algorithms Commented Sep 21, 2017 at 14:26

1 Answer 1

5

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>

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.