javascriptjquery-terminal

How can I create a simple loading animation while a function is running?


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?


Solution

  • @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).