简体   繁体   English

如何通过 API 调用停止递归 setTimeout?

[英]How to stop a recursive setTimeout with an API call?

I have a NextJS application that runs a recursive setTimeout when the server is started.我有一个 NextJS 应用程序,它在服务器启动时运行递归 setTimeout。 I need to create an API endpoint that can start and stop this loop (to have more control over it in production).我需要创建一个可以启动和停止此循环的 API 端点(以便在生产中对其进行更多控制)。 This loop is used to process items in a database that are added from another API endpoint.此循环用于处理数据库中从另一个 API 端点添加的项目。

  import { clearTimeout } from "timers";

  var loopFlag = true;

  export function loopFlagSwitch(flag: boolean) {
    loopFlag = flag;
  }

  export async function loop() {
    
    try {
      // Retrieve all unprocessed transactions
      const unprocessedTransactions = await prisma.transaction.findMany({
        take: 100,
        where: { status: "UNPROCESSED" },
      });

      // Loop through transactions and do stuff
      for (const transaction of unprocessedTransactions) {
        //stuff
      }
    } catch (e) {
      // handle error
    }

    if (loopFlag === true) { 
      setTimeout(loop, 1000);  //if flag changes, this will stop running
    }
  }

  if (require.main === module) {
    loop(); // This is called when server starts, but not when file is imported
  }

The reason I use setTimeout and not setInterval is because many errors can occur when processing items retrieved from DB.我使用 setTimeout 而不是 setInterval 的原因是因为在处理从 DB 检索的项目时会发生很多错误。 These errors, however, are solved by waiting a few milliseconds.但是,这些错误可通过等待几毫秒来解决。 So, the benefit of the pattern below is that if an error happens, the loop immediately restarts and the error will not appear because a ms has passed (it's due to concurrency problems -- let's ignore this for now).所以,下面这个模式的好处是,如果发生错误,循环会立即重新开始,错误不会出现,因为已经过去了一个毫秒(这是由于并发问题——我们暂时忽略它)。

To attempt to start and stop this loop, I have an endpoint that simply calls the loopFlagSwitch function.为了尝试启动和停止此循环,我有一个端点,它只调用 loopFlagSwitch function。

import { NextApiRequest, NextApiResponse } from "next";
import { loopFlagSwitch } from "services/loop";

async function handler(req: NextApiRequest, res: NextApiResponse) {
  try {
    loopFlagSwitch(req.body.flag);
  } catch (error) {
    logger.info({ error: error });
  }
}

export default handler;

Problem is, even when this endpoint is called, the setTimeout loop keeps going.问题是,即使调用此端点,setTimeout 循环也会继续。 Why isn't it picking the change in flag?为什么不选择标志的变化?

clearTimeout()清除超时()

The global clearTimeout() method cancels a timeout previously established by calling setTimeout().全局 clearTimeout() 方法取消先前通过调用 setTimeout() 建立的超时。

To clear a timeout, use the id returned from setTimeout():要清除超时,请使用从 setTimeout() 返回的 ID:

Usage用法

const myTimeout = setTimeout(function, milliseconds);
//Then you can to stop the execution by calling clearTimeout():

clearTimeout(myTimeout);

loopFlag as a condition loopFlag 作为条件

...
if (loopFlag === true) { 
    myTimeout();
} else {
    clearTimeout(myTimeout)
}
...

Add abortTimer function添加中止计时器 function

Full code完整代码



export function loopFlagSwitch(flag) {
  flag === true ? loop : abortTimer()
}

// set timeout
var myTimeout = setTimeout(loop, 1000);


function abortTimer() { // to be called when you want to stop the timer
  clearTimeout(myTimeout);
}

export async function loop() {
    try {
      // Retrieve all unprocessed transactions
      let d = "Retrieve all unprocessed transactions"
      process.stdout.write(d + '\n');

      // Loop through transactions and do stuff
      for (let i = 0; i<10;  i++) {
        //stuff
        let c = "second loop"
        process.stdout.write(c + '\n');
      }
    } catch (e) {
      // handle error
      console.log("error ", e)
    }  finally {
      myTimeout = setTimeout(loop, 1000); // repeat myself 
    }
    
  }

  if (require.main === module) {
    loop(); // This is called when server starts, but not when file is imported
  }

The flag will not work because node doesn't maintain the state of a file, the import only cares about the things it obtains from a file, it doesn't mind about the state of the variables declared in it.该标志将不起作用,因为节点不维护文件的 state,导入只关心它从文件中获取的内容,它不关心其中声明的变量的 state。

Even though the clearTimeout() function may be sufficient, i think there is an even better option you can use to stop this loop.尽管 clearTimeout() function 可能就足够了,但我认为您可以使用一个更好的选项来停止此循环。

Use a JS Class!使用 JS 类!

Instead of using just a function without state. You could instantiate a class that runs on the server with an internal boolean that can be called "shouldKeepLooping" for example:而不是只使用 function 而不使用 state。您可以实例化一个在服务器上运行的 class,内部 boolean 可以称为“shouldKeepLooping”,例如:


class NextJsLooper { // or whatever better name you can use

  private shouldKeepLooping: boolean

  constructor () {
    this.shouldKeepLooping = true
  }
  
  public shouldKeepLooping(value) { this.shouldKeepLooping = value }

  public async loop () {
    if (shouldKeepLooping) {
      //... rest of the loop code
      setTimeout(this.loop, 1000);
    }
  }
}

This way if you set the value to false it will automatically stop.这样,如果您将值设置为 false,它将自动停止。 Since this is a reference to the object.因为这是对 object 的引用。

Keep in mind that you would need to keep this instance alive as probably something global, and would need it to be accesible by nextJS.请记住,您需要让这个实例保持活动状态,因为它可能是全局的,并且需要它可以被 nextJS 访问。

You can use Symbol.for and the Node global to mantain the instance saved somewhere in the server!您可以使用Symbol.for和 Node 全局来维护保存在服务器某处的实例!

What you've tried would work inside a regular web page, because the loopFlag variable would be in global (window) scope.您所尝试的将在常规 web 页面中工作,因为loopFlag变量将位于全局(窗口)scope 中。

Next.js does not share global scope in the same manner. Next.js 不以相同方式共享全局 scope。 Therefore, you need to use a React Context in order to share access to the variable.因此,您需要使用React Context来共享对变量的访问。

My suggestion is to introduce the use of signals.我的建议是引入信号的使用。

Service like Pusher will trigger the event that will be listened by the transaction processor.Pusher这样的服务将触发将由事务处理器侦听的事件。

your transaction processing api / code above or any other您的交易处理 api / 以上代码或任何其他

Signal actions like "start" and "stop" will be triggered anytime even in production by either frontend or through the pusher portal that will be used to change the loop flag to either true or false.即使在生产中,前端或通过用于将循环标志更改为 true 或 false 的推送器门户,也会随时触发“开始”和“停止”等信号操作。

you can retrieve the thread in charge of the operation and interrupt after a given expected time,and log something to inform you about the time out in case that your operation took more than the necessary time.您可以在给定的预期时间后检索负责操作和中断的线程,并记录一些内容以通知您超时,以防您的操作花费的时间超过必要的时间。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM