javascript 中处理函数中断的最佳方法是什么

What is the best way to handle function interrupts in javascript

基本上我有两个异步函数。其中一个是简单的 5 秒超时,另一个是具有多个步骤的复杂异步函数。 这是一个例子

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

我实现这个的方法是添加一个全局变量并在 for 循环中包含一个 if 语句来检查是否已调用中断,但我想知道是否有更好的方法来实现它。这个解决方案的一个问题是它会打印更多的数字。我将不得不向另一个 for 循环添加另一个检查,这可能很快就会变得混乱。

这就是你想要的吗?

改变了什么:

  • Promise.all 替换为 Promise.race
  • 添加了 isStopped 道具,它使 “具有多个步骤的复杂异步函数” 跳过剩余步骤的执行。但是,它不会杀死它。承诺是 .

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>


初始答案 (留在评论中引用它,而不是上面的内容;以防有人发现它在 2074 年有用 ):

这是一个概述我在评论中提出的建议的示例。 run() below returns 1s 之后发生的拒绝者与 02s 之间随机时间解决的实现者之间的竞争。

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`));
})

注意:最初我将拒绝器放在 class 内,但(至少对于上面的示例)我不明白为什么它不应该留在外面(在真实情况下,从助手导入文件)。

如果您需要瞬时停止功能,您可能希望将打印作业作为外部脚本执行。然后你像这样使用子进程。

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'))
    }

}

您将从节点文档网站获得有关如何启动子进程的更多信息https://nodejs.org/api/child_process.html#subprocesskillsignal

如果您的工作(外部脚本)停滞,建议您只有在至少有 2 CPU 个内核的情况下才使用上述代码,否则它会如果你的脚本很重,会影响你的主进程。

这是一个 simple demo that uses my own 库。

import { CPromise } from "c-promise2";

const task = CPromise.promisify(function* () {
  let printStuff = CPromise.promisify(function* () {
    for (let i = 0; i < 10; i++) {
      console.log(i);
      yield CPromise.delay(100);
    }
  });

  const length = Math.floor(Math.random() * 10) + 3;

  //dont know how long this will take
  for (let i = 0; i < length; i++) {
    yield printStuff();
  }

  return "letters";
});

const promise = task()
  .timeout(5000)
  .then(
    (result) => console.log(`Done: ${result}`),
    (err) => console.warn(`Fail: ${err}`)
  );

setTimeout(() => {
  promise.cancel();
}, 2000);