I'm trying to create a simple loading animation for an arbitrary function in JQuery Terminal. I know that JavaScript is single threaded and synchronous, but are there any workarounds?
I've seen the JQuery progress bar animation example, but I'm not sure how I can run an animation similar to that while another function runs on the background. Could I potentially use async and await functions?
Below is a simple reproducible example:
var terminal = $('#terminal').terminal( function(command, term) {
arbitrary_function();
let i = 0;
let loading_animation = ['[LOADING ]', '[LOADING . ]', '[LOADING .. ]', '[LOADING ... ]', '[LOADING ....]']
(function loop() {
terminal.set_prompt(loading_animation[i]);
timer = setTimeout(loop, 550);
i = ((i + 1) % 5);
})();
}
Ideally, I'd want everything below arbitrary_function() to run until arbitrary_function() is done. How can I possibly do that in JavaScript?
@Wyck is right you can use Web Workers for this if your arbitrary_function do long computation, here is example how to do this using Comlink library
const terminal = $('body').terminal(function() {
const promise = arbitrary_function();
// we don't want interactivity, but want prompt to be visible
terminal.pause(true);
let i = 0;
const loading_animation = [
'[LOADING ]',
'[LOADING . ]',
'[LOADING .. ]',
'[LOADING ... ]',
'[LOADING ....]'
];
let stop = false;
const prompt = terminal.get_prompt();
const done = new Promise(resolve => {
(function loop() {
terminal.set_prompt(loading_animation[i]);
if (!stop) {
timer = setTimeout(loop, 550);
} else {
// clear the animation
terminal
.echo(loading_animation[i])
.set_prompt(prompt)
.resume();
resolve();
}
i = ((i + 1) % 5);
})();
});
promise.then(function() {
// long calculation is done
stop = true;
});
done.then(() => promise).then(x => {
terminal.echo(x);
});
});
function arbitrary_function() {
return worker.longTask();
}
const worker = Comlink.wrap(fworker(function() {
importScripts("https://cdn.jsdelivr.net/npm/comlink/dist/umd/comlink.min.js");
Comlink.expose({
longTask() {
let i = 3000000000;
while (i--) {
Math.pow(2, 100000000);
}
return "any value";
}
});
}));
// function to worker
function fworker(fn) {
var str = '(' + fn.toString() + ')()';
// ref: https://stackoverflow.com/a/10372280/387194
var URL = self.URL || self.webkitURL;
var blob;
try {
blob = new Blob([str], { type: 'application/javascript' });
} catch (e) { // Backwards-compatibility
const BlobBuilder = self.BlobBuilder ||
self.WebKitBlobBuilder ||
self.MozBlobBuilder;
blob = new BlobBuilder();
blob.append(str);
blob = blob.getBlob();
}
return new Worker(URL.createObjectURL(blob));
}
<script src="https://cdn.jsdelivr.net/npm/comlink/dist/umd/comlink.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery.terminal/js/jquery.terminal.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/jquery.terminal/css/jquery.terminal.css" rel="stylesheet"/>
NOTE: If your function is just async and doesn't block the main thread you can just use Promises and don't need Web Worker for that.
function arbitrary_function() {
return new Promise(resolve => {
setTimeout(resolve, 1000);
});
}
Instead of setTimeout, you can use any function. The rest of the code will be the same (but without a Web Worker).