I have a NextJS application that runs a recursive setTimeout when the server is started. I need to create an API endpoint that can start and stop this loop (to have more control over it in production). This loop is used to process items in a database that are added from another API endpoint.
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. 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.
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. Why isn't it picking the change in flag?
What you've tried would work inside a regular web page, because the loopFlag variable would be in global (window) scope.
Next.js does not share global scope in the same manner. Therefore, you need to use a React Context in order to share access to the variable.
My suggestion is to introduce the use of signals.
Service like Pusher will trigger the event that will be listened by the transaction processor.
your transaction processing api / code above or any other
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.