Skip to content

Instantly share code, notes, and snippets.

@makadev
Last active July 29, 2022 13:23
Show Gist options
  • Save makadev/d3620995cd99917f9cc96c0d26d7c3aa to your computer and use it in GitHub Desktop.
Save makadev/d3620995cd99917f9cc96c0d26d7c3aa to your computer and use it in GitHub Desktop.
Simple endless loop (Looper) for NodeJS/TS (or ts-node)
/**
* Loop Handler Type
*/
type LoopHandler = () => void | never | boolean | Promise<void> | Promise<boolean>;
/**
* Shutdown Handler Type
*/
type ShutdownHandler = () => void;
/**
* Custom Error Class
*/
class LooperError extends Error {}
/**
* Exception for Fast Exit
*/
class LooperExit extends Error {}
/**
* Looper Singleton
*/
class Looper {
private static looper?: Looper;
public static logHandler = (message: string, extra: string) => {
console.log(`${message}: ${extra}`);
};
/**
* Set true if looper should not reschedule the handler
*
* @static
* @type {boolean}
* @memberof Looper
*/
public static shouldShutdown: boolean = false;
/**
* Time in ms to wait between reschedule to reduce CPU Usage
*
* @static
* @type {number}
* @memberof Looper
*/
public static wait: number = 1000;
/**
* Loop handler to be executed every cycle
*/
public handler: LoopHandler = () => {
throw new LooperError("No Loophandler, No Looping, Bye");
};
/**
* Handler that will be called on CTRL-C or OS Termination request (SIGINT/SIGTERM)
*/
public static shutdownHandler: ShutdownHandler = () => {
Looper.shouldShutdown = true;
};
/**
* Cycle runner
*
* @returns
*/
private async run() {
const _instance = Looper.looper;
if (_instance === undefined) {
throw new LooperError("No Looper, No Looping, Bye");
}
// check shutdown condition
if (Looper.shouldShutdown) {
Looper.logHandler("Graceful Shutdown", "bye");
return;
}
// execute handler
const handler = _instance.handler;
try {
const res = await handler();
if (typeof res === "boolean" && res === false) {
Looper.logHandler("Looper Exit", "termination request");
return;
}
} catch (e) {
if (e instanceof LooperExit) {
Looper.logHandler("Looper Exit", e.message);
return;
}
throw e;
}
// again, check shutdown condition
if (Looper.shouldShutdown) {
Looper.logHandler("Graceful Shutdown", "bye");
return;
}
// reschedule for another cycle
setTimeout(_instance.run, Looper.wait);
}
/**
* Register handler and startup loop
*
* @param handler
* @param registerShutdownHandler
* @returns
*/
public static async loop(handler: LoopHandler, registerShutdownHandler: boolean = true) {
const instance = new Looper();
instance.handler = handler;
Looper.looper = instance;
if (registerShutdownHandler) {
process.on("SIGTERM", Looper.shutdownHandler);
process.on("SIGINT", Looper.shutdownHandler);
}
await instance.run();
return instance;
}
}
/*
* Exec
*/
let i = 0;
Looper.loop(() => {
i++;
if (i > 10) throw new LooperExit("no more meeeps!!!");
console.log(`meeeep ${i}`);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment