From 39c985dc4660d581a776d8a14bb680414d9fe040 Mon Sep 17 00:00:00 2001 From: vadim Date: Thu, 12 Jun 2025 13:28:37 +0300 Subject: [PATCH 1/7] Improve TimeoutCollection compatibility --- JavaScript/1-prototype.js | 116 +++++++++++++++++++++++++------------- 1 file changed, 77 insertions(+), 39 deletions(-) diff --git a/JavaScript/1-prototype.js b/JavaScript/1-prototype.js index fbec828..5cc6d07 100644 --- a/JavaScript/1-prototype.js +++ b/JavaScript/1-prototype.js @@ -3,55 +3,93 @@ // Facade that wraps Map and Node.js Timers to provide a simple interface for a // collection with values that have expiration timeout. -const TimeoutCollection = function (timeout) { - this.timeout = timeout; - this.collection = new Map(); - this.timers = new Map(); -}; - -TimeoutCollection.prototype.set = function (key, value) { - const timer = this.timers.get(key); - if (timer) clearTimeout(timer); - const timeout = setTimeout(() => { +class TimeoutCollection { + constructor(timeout) { + this.timeout = timeout; + this.collection = new Map(); + this.timers = new Map(); + this.isNode = typeof process !== 'undefined' && process.versions?.node; + } + + set(key, value) { this.delete(key); - }, this.timeout); - timeout.unref(); - this.collection.set(key, value); - this.timers.set(key, timeout); -}; -TimeoutCollection.prototype.get = function (key) { - return this.collection.get(key); -}; + const timeoutId = setTimeout(() => { + this.delete(key); + }, this.timeout); + + if (this.isNode && typeof timeoutId.unref === 'function') { + timeoutId.unref(); + } -TimeoutCollection.prototype.delete = function (key) { - const timer = this.timers.get(key); - if (timer) { - clearTimeout(timer); - this.collection.delete(key); - this.timers.delete(key); + this.collection.set(key, value); + this.timers.set(key, timeoutId); } -}; -TimeoutCollection.prototype.toArray = function () { - return [...this.collection.entries()]; -}; + get(key) { + return this.collection.get(key); + } -// Usage + delete(key) { + const timer = this.timers.get(key); + if (timer) { + clearTimeout(timer); + this.timers.delete(key); + this.collection.delete(key); + return true; + } + return false; + } + + clear() { + for (const timer of this.timers.values()) { + clearTimeout(timer); + } + this.timers.clear(); + this.collection.clear(); + } + + has(key) { + return this.collection.has(key); + } -const hash = new TimeoutCollection(1000); -hash.set('uno', 1); -console.dir({ array: hash.toArray() }); + size() { + return this.collection.size; + } + + toArray() { + return [...this.collection.entries()]; + } + + destroy() { + this.clear(); + } +} -hash.set('due', 2); -console.dir({ array: hash.toArray() }); +if (typeof module !== 'undefined' && module.exports) { + module.exports = TimeoutCollection; +} else { + window.TimeoutCollection = TimeoutCollection; +} -setTimeout(() => { - hash.set('tre', 3); +// Usage + +if (typeof process !== 'undefined' && process.versions?.node) { + const hash = new TimeoutCollection(1000); + hash.set('uno', 1); + console.dir({ array: hash.toArray() }); + + hash.set('due', 2); console.dir({ array: hash.toArray() }); setTimeout(() => { - hash.set('quattro', 4); + hash.set('tre', 3); console.dir({ array: hash.toArray() }); - }, 500); -}, 1500); + + setTimeout(() => { + hash.set('quattro', 4); + console.dir({ array: hash.toArray() }); + hash.destroy(); + }, 500); + }, 1500); +} From e7b218a16e8918df9de8e613b95427f7b3f768e7 Mon Sep 17 00:00:00 2001 From: vadim Date: Thu, 12 Jun 2025 13:41:06 +0300 Subject: [PATCH 2/7] Make TimeoutCollection Map-compatible --- JavaScript/2-class.js | 43 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/JavaScript/2-class.js b/JavaScript/2-class.js index 094cf18..fee1b1b 100644 --- a/JavaScript/2-class.js +++ b/JavaScript/2-class.js @@ -13,31 +13,68 @@ class TimeoutCollection { const timeout = setTimeout(() => { this.delete(key); }, this.timeout); - timeout.unref(); + if (typeof timeout.unref === 'function') timeout.unref(); this.collection.set(key, value); this.timers.set(key, timeout); + return this; } get(key) { return this.collection.get(key); } + has(key) { + return this.collection.has(key); + } + delete(key) { const timer = this.timers.get(key); if (timer) { clearTimeout(timer); - this.collection.delete(key); this.timers.delete(key); + return this.collection.delete(key); + } + return false; + } + + clear() { + for (const timer of this.timers.values()) { + clearTimeout(timer); } + this.timers.clear(); + this.collection.clear(); + } + + size() { + return this.collection.size; } toArray() { return [...this.collection.entries()]; } + + forEach(callbackfn, thisArg) { + return this.collection.forEach(callbackfn, thisArg); + } + + keys() { + return this.collection.keys(); + } + + values() { + return this.collection.values(); + } + + entries() { + return this.collection.entries(); + } + + [Symbol.iterator]() { + return this.collection[Symbol.iterator](); + } } // Usage - const hash = new TimeoutCollection(1000); hash.set('uno', 1); console.dir({ array: hash.toArray() }); From ea4e4647dccb9d98bfdd758daa22ff9f69daa7d3 Mon Sep 17 00:00:00 2001 From: vadim Date: Thu, 12 Jun 2025 13:54:29 +0300 Subject: [PATCH 3/7] Optimize timeoutCollection for V8 --- JavaScript/3-function.js | 49 +++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/JavaScript/3-function.js b/JavaScript/3-function.js index 89821ea..528bde6 100644 --- a/JavaScript/3-function.js +++ b/JavaScript/3-function.js @@ -4,37 +4,34 @@ const timeoutCollection = (interval) => { const collection = new Map(); const timers = new Map(); - const instance = {}; - - instance.set = (key, value) => { - const timer = timers.get(key); - if (timer) clearTimeout(timer); - const timeout = setTimeout(() => { - collection.delete(key); - }, interval); - timeout.unref(); - collection.set(key, value); - timers.set(key, timer); - }; - - instance.get = (key) => collection.get(key); - - instance.delete = (key) => { - const timer = timers.get(key); - if (timer) { - clearTimeout(timer); - collection.delete(key); - timers.delete(key); + return { + set(key, value) { + const existingTimer = timers.get(key); + if (existingTimer) clearTimeout(existingTimer); + const timeout = setTimeout(() => collection.delete(key), interval); + if (typeof timeout.unref === 'function') timeout.unref(); + collection.set(key, value); + timers.set(key, timeout); + return this; + }, + get(key) { + return collection.get(key); + }, + delete(key) { + const timer = timers.get(key); + if (timer) { + clearTimeout(timer); + collection.delete(key); + timers.delete(key); + } + }, + toArray() { + return [...collection.entries()]; } }; - - instance.toArray = () => [...collection.entries()]; - - return instance; }; // Usage - const hash = timeoutCollection(1000); hash.set('uno', 1); console.dir({ array: hash.toArray() }); From 0aec62aa4d1974edefdbe03862212cf60aaa456f Mon Sep 17 00:00:00 2001 From: vadim Date: Thu, 12 Jun 2025 14:04:53 +0300 Subject: [PATCH 4/7] Refactor timeoutCollection to use expiration times --- JavaScript/3-function.js | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/JavaScript/3-function.js b/JavaScript/3-function.js index 528bde6..da714ba 100644 --- a/JavaScript/3-function.js +++ b/JavaScript/3-function.js @@ -2,31 +2,35 @@ const timeoutCollection = (interval) => { const collection = new Map(); - const timers = new Map(); + const expirations = new Map(); return { set(key, value) { - const existingTimer = timers.get(key); - if (existingTimer) clearTimeout(existingTimer); - const timeout = setTimeout(() => collection.delete(key), interval); - if (typeof timeout.unref === 'function') timeout.unref(); + const expiration = Date.now() + interval; collection.set(key, value); - timers.set(key, timeout); + expirations.set(key, expiration); return this; }, get(key) { + this.cleanup(); return collection.get(key); }, delete(key) { - const timer = timers.get(key); - if (timer) { - clearTimeout(timer); - collection.delete(key); - timers.delete(key); - } + collection.delete(key); + expirations.delete(key); }, toArray() { + this.cleanup(); return [...collection.entries()]; + }, + cleanup() { + const now = Date.now(); + for (const [key, expiration] of expirations.entries()) { + if (now >= expiration) { + collection.delete(key); + expirations.delete(key); + } + } } }; }; @@ -46,5 +50,6 @@ setTimeout(() => { setTimeout(() => { hash.set('quattro', 4); console.dir({ array: hash.toArray() }); + hash.cleanup(); // Manually trigger cleanup }, 500); }, 1500); From f5af0d933e56f734dd478e97961f6b9b45afe8fc Mon Sep 17 00:00:00 2001 From: vadim Date: Thu, 12 Jun 2025 14:14:35 +0300 Subject: [PATCH 5/7] Improve Scheduler logging --- JavaScript/4-scheduler.js | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/JavaScript/4-scheduler.js b/JavaScript/4-scheduler.js index 953c9f5..f5d2113 100644 --- a/JavaScript/4-scheduler.js +++ b/JavaScript/4-scheduler.js @@ -3,14 +3,24 @@ const { EventEmitter } = require('node:events'); class Logger { - static color(level) { + static COLORS = { + warn: '\x1b[1;33m', + error: '\x1b[0;31m', + info: '\x1b[1;37m', + }; + + constructor(output = console) { + this.output = output; + } + + color(level) { return Logger.COLORS[level] || Logger.COLORS.info; } log(level, s) { const date = new Date().toISOString(); - const color = Logger.color(level); - console.log(color + date + '\t' + s + '\x1b[0m'); + const color = this.color(level); // Use this to access instance method + this.output.log(color + date + '\t' + s + '\x1b[0m'); } warn(s) { @@ -26,12 +36,6 @@ class Logger { } } -Logger.COLORS = { - warn: '\x1b[1;33m', - error: '\x1b[0;31m', - info: '\x1b[1;37m', -}; - class Task extends EventEmitter { constructor(name, time, exec) { super(); @@ -88,10 +92,10 @@ class Task extends EventEmitter { } class Scheduler extends EventEmitter { - constructor() { + constructor({ options = { output: console } } = {}) { super(); this.tasks = new Map(); - this.logger = new Logger(); + this.logger = new Logger(options.output); } task(name, time, exec) { @@ -99,14 +103,14 @@ class Scheduler extends EventEmitter { const task = new Task(name, time, exec); this.tasks.set(name, task); task.on('error', (err) => { - this.logger.error(task.name + '\t' + err.message); + this.logger.error(`${task.name}\t${err.message}`); this.emit('error', err, task); }); task.on('begin', () => { - this.logger.info(task.name + '\tbegin'); + this.logger.info(`${task.name}\tbegin`); }); task.on('end', (res = '') => { - this.logger.warn(task.name + '\tend\t' + res); + this.logger.warn(`${task.name}\tend\t${res}`); }); task.start(); return task; @@ -128,8 +132,7 @@ class Scheduler extends EventEmitter { } // Usage - -const scheduler = new Scheduler(); +const scheduler = new Scheduler({ options: { output: console } }); scheduler.on('error', (err, task) => { console.log(`Error in ${task.name}:\n ${err.stack}`); From 48ae030db433dede1f786354548163afb64bf659 Mon Sep 17 00:00:00 2001 From: vadim Date: Thu, 12 Jun 2025 14:21:04 +0300 Subject: [PATCH 6/7] Refactor Scheduler to use Task subclasses for scheduling types --- JavaScript/4-scheduler.js | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/JavaScript/4-scheduler.js b/JavaScript/4-scheduler.js index f5d2113..9433caf 100644 --- a/JavaScript/4-scheduler.js +++ b/JavaScript/4-scheduler.js @@ -19,7 +19,7 @@ class Logger { log(level, s) { const date = new Date().toISOString(); - const color = this.color(level); // Use this to access instance method + const color = this.color(level); this.output.log(color + date + '\t' + s + '\x1b[0m'); } @@ -37,18 +37,9 @@ class Logger { } class Task extends EventEmitter { - constructor(name, time, exec) { + constructor(name, exec) { super(); this.name = name; - if (typeof time === 'number') { - this.time = Date.now() + time; - this.set = setInterval; - this.clear = clearInterval; - } else { - this.time = new Date(time).getTime(); - this.set = setTimeout; - this.clear = clearTimeout; - } this.exec = exec; this.running = false; this.count = 0; @@ -91,6 +82,24 @@ class Task extends EventEmitter { } } +class IntervalTask extends Task { + constructor(name, time, exec) { + super(name, exec); + this.time = Date.now() + time; + this.set = setInterval; + this.clear = clearInterval; + } +} + +class TimeoutTask extends Task { + constructor(name, time, exec) { + super(name, exec); + this.time = new Date(time).getTime(); + this.set = setTimeout; + this.clear = clearTimeout; + } +} + class Scheduler extends EventEmitter { constructor({ options = { output: console } } = {}) { super(); @@ -100,7 +109,9 @@ class Scheduler extends EventEmitter { task(name, time, exec) { this.stop(name); - const task = new Task(name, time, exec); + const task = typeof time === 'number' + ? new IntervalTask(name, time, exec) + : new TimeoutTask(name, time, exec); this.tasks.set(name, task); task.on('error', (err) => { this.logger.error(`${task.name}\t${err.message}`); @@ -139,13 +150,13 @@ scheduler.on('error', (err, task) => { //process.exit(1); }); -scheduler.task('name1', '2019-03-12T14:37Z', (done) => { +scheduler.task('name1', '2035-03-12T14:37Z', (done) => { setTimeout(() => { done(null, 'task successed'); }, 1000); }); -scheduler.task('name2', '2019-03-12T14:37Z', (done) => { +scheduler.task('name2', '2035-03-12T14:37Z', (done) => { setTimeout(() => { done(new Error('task failed')); }, 1100); From 88744919cc6990046f067554d4369ae7fee85e51 Mon Sep 17 00:00:00 2001 From: vadim Date: Thu, 12 Jun 2025 14:58:29 +0300 Subject: [PATCH 7/7] Add .d.ts --- JavaScript/.d.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 JavaScript/.d.ts diff --git a/JavaScript/.d.ts b/JavaScript/.d.ts new file mode 100644 index 0000000..aa8e4e0 --- /dev/null +++ b/JavaScript/.d.ts @@ -0,0 +1,22 @@ +declare class TimeoutCollection { + constructor(timeout: number); + + set(key: K, value: V): this; + get(key: K): V | undefined; + has(key: K): boolean; + delete(key: K): boolean; + clear(): void; + size(): number; + toArray(): [K, V][]; + forEach(callbackfn: (value: V, key: K, map: Map) => void, thisArg?: any): void; + keys(): IterableIterator; + values(): IterableIterator; + entries(): IterableIterator<[K, V]>; + [Symbol.iterator](): IterableIterator<[K, V]>; + + private collection: Map; + private timers: Map; + private timeout: number; +} + +export = TimeoutCollection;