SOLVED: Updating async functions between modules

59 Views Asked by At

I have been working on a simple loading animation for backend node.js projects and I ran into a problem after trying to modularize it (so that I can just require() it in other projects)

When run, this loading bar creates a spinner out of braille characters in the console and tries to take three inputs (which used to be global variables before I tried to encapsulate the function, however, when that didn't work I added them to the exported variables in module.exports):

  1. totalSteps: the total # of steps the given function has.
  2. currentStep: the current step the function is on.
  3. statusMessage: the message to display while the spinner loads.

every 100ms, the spinner function updates the frame on the animation which includes the spinner, a percentage of complete tasks, the fraction of done vs not done steps, the message, and three animated trailing dots.

for some reason I can not get this function to work now that I have put it in its own module. It does not wait for the provided function to end before it prints its "Done!" message. however, the variables do seem to be shared between the two scripts, as the final "Done!" message includes the message, and the step variables in it.

Here's my code so far for the loading.js file:

module.exports = {
    currentStep : 0,
    totalSteps : 0,
    statusMessage : '',

    spinner : async function (func) {
        // declare array of frames for the animation
        const spinnerAnimation = ['[⠋]', '[⠙]', '[⠹]', '[⠸]', '[⠼]', '[⠴]', '[⠦]', '[⠧]', '[⠇]', '[⠏]'];
        const dots = ['   ', '   ', '   ', '.', '.', '.', '.. ', '.. ', '.. ', '...', '...', '...'];
        let spinnerCounter = 0;
        let dotCounter = 0;
        // overwrite the line with the new frame every 100ms
        const intervalId = setInterval(() => {
            spinnerCounter = (spinnerCounter + 1) % spinnerAnimation.length;
            dotCounter = (dotCounter + 1) % dots.length;
            if (this.statusMessage == '') this.statusMessage = 'Loading';
            if (this.currentStep <= this.totalSteps && this.currentStep >= 0 && this.totalSteps >= 1) {
                // if the variables used for calculating the % value are in the correct range
                // show the % and the # of steps completed out of the total
                process.stdout.write(`\r\x1b[32m\x1b[?25l${spinnerAnimation[spinnerCounter]} ${Math.round(this.currentStep / this.totalSteps * 10000)/100}% (Step: ${this.currentStep}/${this.totalSteps} complete)\x1b[0m - ${this.statusMessage}${dots[dotCounter]}                                  `);
            } else {
                // if not, just dont show them. that way if it fails, the animation still shows but you dont get any weird errors (or NaN/undefined placeholders)
                process.stdout.write(`\r\x1b[32m\x1b[?25l${spinnerAnimation[spinnerCounter]}\x1b[0m - ${this.statusMessage}${dots[dotCounter]}                                        `);
            }
    
        }, 100);
    
        let error = null;
        try {
            // await some async operation
            await func();
        } catch (err) {
            error = err
        } finally {
            clearInterval(intervalId);
            if (this.statusMessage == '') this.statusMessage = 'Loading';
            if (error) {
                // if an error was caught, mark the bar as "Failed!" and post the message
                if (this.currentStep <= this.totalSteps && this.currentStep >= 0 && this.totalSteps >= 1) {
                    process.stdout.write(`\r\x1b[31m[✓] ${Math.round(this.currentStep / this.totalSteps * 10000)/100}% (Step: ${this.currentStep}/${this.totalSteps})\x1b[0m - ${this.statusMessage}... >> FAILED!\x1b[?25h                                                \n`);
                } else {
                    process.stdout.write(`\r\x1b[32m[✓]\x1b[0m - ${this.statusMessage}... >> FAILED!\x1b[?25h                                                \n`);
                }
                console.error(`\x1b[31m └-${error}\x1b[?25h\x1b[0m`);
                this.currentStep = 0;
                this.totalSteps = 0;
                this.statusMessage = '';
                return error;
            } else {
                // if there was no error, stop the bar and return to the script
                if (this.currentStep <= this.totalSteps && this.currentStep >= 0 && this.totalSteps >= 1) {
                    process.stdout.write(`\r\x1b[32m[✓] ${Math.round(this.currentStep / this.totalSteps * 10000)/100}% (Step: ${this.currentStep}/${this.totalSteps})\x1b[0m - ${this.statusMessage}... >> Done!\x1b[?25h                                                \n`);
                } else {
                    process.stdout.write(`\r\x1b[32m[✓]\x1b[0m - ${this.statusMessage}... >> Done!\x1b[?25h                                                \n`);
                }
                this.currentStep = 0;
                this.totalSteps = 0;
                this.statusMessage = '';
                return;
            }
        }
    }
}

And for the index.js:

const loading = require('./loading.js');


const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function something() {
  loading.totalSteps = 3;
  loading.statusMessage = "doing something";
  sleep(20000);
  loading.currentStep = 1;
  loading.statusMessage = "working harder";
  sleep(2000);
  loading.currentStep = 2;
  loading.statusMessage = "almost done";
  sleep(2000);
  loading.currentStep = 3;
}


async function main() {
  await loading.spinner(something);
}

main();

The output for index.js script is as follows:

[✓] 100% (Step: 3/3) - almost done... >> Done!

This is shown almost as soon as it is run, without waiting for the something() function to finish waiting, however the program does not close until the something() function finishes waiting. I don't understand why the spinner never shows up though. any ideas?

on a side note, Is there any way to make it so that the entire line is overwritten every frame? for example, if the statusMessage text is shorter than the frame before it, the end of the last frame persists into the second one because the rest was not overwritten with new text. My hacky solution to this was to just append a lot of spaces to the end of each line so that so long as the message text is short you wont see it but I realize it wont fix the problem, how does one measure the length of the last line and append the correct number of spaces to the end of the new line or clear the line entirely before writing the next one.

I understand this is probably a lot of stuff to go through so I apologize. Thank you in advance! If there's any other information I should provide please ask!

0

There are 0 best solutions below