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.
解决方案
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:
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;
}
};