Home > Software engineering >  What is the best way to handle function interrupts in javascript
What is the best way to handle function interrupts in javascript

Time:01-21

Basically I have two async functions. One of them is a simple 5 second timeout and the other is a complicated async function with multiple steps. Here is an example

const delay = ms => new Promise(res => setTimeout(res, ms));

class Runner {
  
  async start() {

      let printStuff = async () => {
        for(let i = 0 ; i < 50; i  ){
          console.log(i);
          await delay(50);
        }
      }


      let printLetters = new Promise(async function(resolve, reject) {
        
        const length = Math.floor(Math.random() * 10)
        
        //dont know how long this will take
        for(let i = 0; i < length; i  ){
          await printStuff();
        }
        
        resolve('letters');
      });

      let timeout = new Promise(async function(resolve, reject) {
        await delay(5000);
        resolve('timeout')
      });
      
      const finished = await Promise.all([timeout, printLetters]);
      if(finished === 'timeout'){
        this.stop();
      }
  }
  
  stop(){
    //stop printing stuff instantly
  }

}

const myRunner = new Runner();

myRunner.start();
//could call myRunner.stop(); if the user canceled it

The way I would implement this would add a global variable and include an if statement inside the for loop to check if the interrupt has been called but I am wondering if there is a better way to implement this. An issue with this solution would be it would print a few more numbers. I would have to add another check to the other for loop and this could get messy quickly.

CodePudding user response:

Is this what you're after?

What changed:

  • Promise.all replaced with Promise.race
  • added isStopped prop which makes the "complicated async function with multiple steps" skip execution for the remaining steps. it doesn't kill it, though. Promises are not cancelable.

const delay = ms => new Promise(res => setTimeout(res, ms));

class Runner {
  isStopped = false;

  async start() {

    const printStuff = async () => {
      let i = 0;
      while (!this.isStopped) {
        console.log(i  );
        await delay(50);
      }
    }

    const printLetters = new Promise(
      resolve => printStuff()
        .then(() => resolve('letters'))
    );

    const timeout = new Promise(
      resolve => delay(5000)
        .then(() => resolve('timeout'))
    );

    const finished = await Promise.race([timeout, printLetters]);

    console.log({ finished });

    if (finished === 'timeout') {
      this.stop();
    }
  }

  stop() {
    this.isStopped = true;
  }
}

const myRunner = new Runner();

myRunner.start();
<button onclick="myRunner.stop()">stop</button>


Initial answer (left it in as the comments reference it, not what's above; and in case someone finds it useful in 2074):

Here's an example outlining what I was suggesting in the comment. run() below returns a race between a rejector happening after 1s and a fulfiller which resolves in random time between 0 and 2s.

const rejector = (timeout) => new Promise((resolve, reject) => {
  setTimeout(reject, timeout, 'rejected')
});

class Runner {
  run() {
    return Promise.race([
      rejector(1000), 
      new Promise((resolve, reject) => {
        setTimeout(resolve, Math.random() * 2000, 'fulfilled')
      })
    ])
  }
}

const t0 = performance.now();
[...Array(6).fill()].forEach((_, key) => {
  const runner = new Runner();
  runner.run()
    .then(r => console.log(`Proomise ${key} ${r} after ${performance.now() - t0}ms`))
    .catch(err => console.log(`Promise ${key} ${err} after ${performance.now() - t0}ms`));
})

Note: initially I placed the rejector inside the class but (at least for the above example) I don't see why it should not stay outside (in a real case scenario, imported from a helper file).

CodePudding user response:

If you require an instantaneous stop capability, you would probably want to execute the print job as a external script. Then you use child processes like this.

const { spawn } = require('child_process');

class Runner {

......


    start() {
        this.job[somejobId] = spawn('command to execute script');
        //this can be anything, including a node script, e.g. `node start.js`
        .....
     }
    

    stop(jobId) {
       if (jobId) {
          //this will kill the script that you spawn above
          this.job[jobId].kill('SIGHUP');
       } 
    }

    stopAllJobs() {
         // looping through the job queue to kill all the jobs
         this.job.forEach(job => job.kill('SIGHUP'))
    }

}

You will have more info on how to start a child process from the node doc website https://nodejs.org/api/child_process.html#subprocesskillsignal

If your job (external script) is stalling, it's recommended that you only use the above codes if you have a minimum 2 CPU core, else it will affect your main process if your script is heavy.

  •  Tags:  
  • Related