class TaskKey {
constructor(namespace, id) {
this.namespace = namespace;
this.id = id;
}
toMapKey() {
return JSON.stringify([this.namespace, this.id]);
}
}
class TaskScheduler {
constructor() {
this.tasks = new Map();
}
schedule(key, task, delayMs) {
this.#validateKey(key);
if (typeof task !== "function") {
throw new TypeError("task must be a function");
}
if (!Number.isFinite(delayMs) || delayMs < 0) {
throw new RangeError("delayMs must be a non-negative number");
}
const mapKey = key.toMapKey();
this.cancel(key);
const scheduledAt = Date.now();
const runAt = scheduledAt + delayMs;
const timerId = setTimeout(async () => {
this.tasks.delete(mapKey);
try {
await task();
} catch (error) {
console.error(`Task failed for key ${mapKey}:`, error);
}
}, delayMs);
this.tasks.set(mapKey, {
key,
task,
timerId,
scheduledAt,
runAt,
});
}
cancel(key) {
this.#validateKey(key);
const mapKey = key.toMapKey();
const scheduledTask = this.tasks.get(mapKey);
if (!scheduledTask) {
return false;
}
clearTimeout(scheduledTask.timerId);
this.tasks.delete(mapKey);
return true;
}
has(key) {
this.#validateKey(key);
return this.tasks.has(key.toMapKey());
}
get(key) {
this.#validateKey(key);
const scheduledTask = this.tasks.get(key.toMapKey());
if (!scheduledTask) {
return null;
}
return {
key: scheduledTask.key,
scheduledAt: scheduledTask.scheduledAt,
runAt: scheduledTask.runAt,
remainingMs: Math.max(0, scheduledTask.runAt - Date.now()),
};
}
clear() {
for (const scheduledTask of this.tasks.values()) {
clearTimeout(scheduledTask.timerId);
}
this.tasks.clear();
}
get size() {
return this.tasks.size;
}
#validateKey(key) {
if (!(key instanceof TaskKey)) {
throw new TypeError("key must be an instance of TaskKey");
}
}
}
const scheduler = new TaskScheduler();
const key = new TaskKey("report", 10);
scheduler.schedule(
key,
() => console.log("Generate report"),
500
);
console.log(scheduler.has(key));
scheduler.cancel(key);
console.log(scheduler.has(key));