diff --git a/CHANGELOG.md b/CHANGELOG.md index 8262b97..a6e774c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [UNRELEASED] (nothing yet) -## [4.0.2+] +## [5.0.0] - 2023-05-07 +### Added +- `startOnly` method +- `restartOnly` method ### Dev - got rid of default exports in internal files, if you (incorrectly) required internal files previously change your `require`/`import` statements ### Changed - tutorial/readme update +- the instance is no longer returned from `start`/`stop` methods, it was useless and possibly confusing ## [4.0.2] - 2023-04-25 ### Dev diff --git a/package.json b/package.json index cad89b6..4a499f6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oop-timers", - "version": "4.0.2+", + "version": "5.0.0", "repository": "git@github.com:dzek69/oop-timers.git", "author": "Jacek Nowacki", "license": "MIT", diff --git a/src/Interval.spec.ts b/src/Interval.spec.ts index 22740af..193ee1f 100644 --- a/src/Interval.spec.ts +++ b/src/Interval.spec.ts @@ -46,7 +46,7 @@ describe("Interval", () => { interval.started.must.be.false(); }); - it("doesn't start the interval by defualt", async () => { + it("doesn't start the interval by default", async () => { const callback = spy(); const interval = new Interval(callback, 100); interval.started.must.be.false(); @@ -346,4 +346,77 @@ describe("Interval", () => { interval.stop(); }); + + it("allows to start the timer only if not started", async () => { + { + const callback = spy(); + const interval = new Interval(callback, 100); + const r1 = interval.startOnly(); + await wait(50); + // will not restart + const r2 = interval.startOnly(); + await wait(50); + // will not restart - interval is running until it is stopped! + const r3 = interval.startOnly(); + await wait(50); + const r4 = interval.startOnly(); + await wait(200); + callback.__spy.calls.length.must.be.equal(3); // 3 calls because wait(200) is 2 intervals + + r1.must.be.true(); + r2.must.be.false(); + r3.must.be.false(); + r4.must.be.false(); + + interval.stop(); + } + + { + const callback = spy(); + const interval = new Interval(callback, 100); + // it will keep restarting, so it will call the callback only once + interval.start(); + await wait(50); + interval.start(); + await wait(50); + interval.start(); + await wait(50); + interval.start(); + await wait(200); + callback.__spy.calls.length.must.be.equal(1); + + interval.stop(); + } + }); + + it("allows to restart the timer only if alredy started", async () => { + const callback = spy(); + const interval = new Interval(callback, 100); + const r1 = interval.restartOnly(); + r1.must.be.false(); + + await wait(200); + callback.__spy.calls.length.must.be.equal(0); + + interval.start(); + const r2 = interval.restartOnly(); + await wait(50); + const r3 = interval.restartOnly(); + await wait(50); + const r4 = interval.restartOnly(); + await wait(50); + const r5 = interval.restartOnly(); + await wait(50); + const r6 = interval.restartOnly(); + await wait(200); + + r2.must.be.true(); + r3.must.be.true(); + r4.must.be.true(); + r5.must.be.true(); + r6.must.be.true(); + + callback.__spy.calls.length.must.equal(1); + interval.stop(); + }); }); diff --git a/src/Interval.ts b/src/Interval.ts index bfd1567..b2d21fd 100644 --- a/src/Interval.ts +++ b/src/Interval.ts @@ -45,13 +45,41 @@ class Interval { this._instantFirstRun = instantFirstRun; } this.stop(); + this._start(); + } + + private _start() { if (this._time !== Infinity) { this._timerId = setInterval(this._cb, this._time); } if (this._instantFirstRun) { this._cb(); } - return this; + } + + /** + * Starts the interval only if it's not already started + * @returns {boolean} - true if newly started, false if already started + */ + public startOnly() { + if (this._timerId !== null) { + return false; + } + this._start(); + return true; + } + + /** + * Restarts the interval only if it's already started + * @returns {boolean} - true if restarted, false if not started + */ + public restartOnly() { + if (this._timerId === null) { + return false; + } + this.stop(); + this._start(); + return true; } /** @@ -60,15 +88,14 @@ class Interval { * @returns {Interval} current instance */ public stop() { - if (this._timerId) { + if (this._timerId !== null) { clearInterval(this._timerId); this._timerId = null; } - return this; } public get started() { - return this._timerId != null; + return this._timerId !== null; } } diff --git a/src/Timeout.spec.ts b/src/Timeout.spec.ts index 472a3f6..c2e4350 100644 --- a/src/Timeout.spec.ts +++ b/src/Timeout.spec.ts @@ -20,7 +20,7 @@ describe("Timeout", () => { timer.started.must.be.false(); }); - it("doesn't fire the callback by defualt", async () => { + it("doesn't fire the callback by default", async () => { const callback = spy(); const timer = new Timeout(callback, 250); @@ -226,4 +226,72 @@ describe("Timeout", () => { timeout.stop(); timeout2.stop(); }); + + it("allows to start the timer only if not started", async () => { + { + const callback = spy(); + const timeout = new Timeout(callback, 100); + const r1 = timeout.startOnly(); + await wait(50); + // will not restart + const r2 = timeout.startOnly(); + await wait(50); + // should start again + const r3 = timeout.startOnly(); + await wait(50); + const r4 = timeout.startOnly(); + await wait(200); + callback.__spy.calls.length.must.be.gt(1); + + r1.must.be.true(); + r2.must.be.false(); + r3.must.be.true(); + r4.must.be.false(); + } + + { + const callback = spy(); + const timeout = new Timeout(callback, 100); + // it will keep restarting, so it will call the callback only once + timeout.start(); + await wait(50); + timeout.start(); + await wait(50); + timeout.start(); + await wait(50); + timeout.start(); + await wait(200); + callback.__spy.calls.length.must.be.equal(1); + } + }); + + it("allows to restart the timer only if alredy started", async () => { + const callback = spy(); + const timeout = new Timeout(callback, 100); + const r1 = timeout.restartOnly(); + r1.must.be.false(); + + await wait(200); + callback.__spy.calls.length.must.be.equal(0); + + timeout.start(); + const r2 = timeout.restartOnly(); + await wait(50); + const r3 = timeout.restartOnly(); + await wait(50); + const r4 = timeout.restartOnly(); + await wait(50); + const r5 = timeout.restartOnly(); + await wait(50); + const r6 = timeout.restartOnly(); + await wait(200); + + r2.must.be.true(); + r3.must.be.true(); + r4.must.be.true(); + r5.must.be.true(); + r6.must.be.true(); + + callback.__spy.calls.length.must.equal(1); + }); }); diff --git a/src/Timeout.ts b/src/Timeout.ts index 3f76ec8..ed44297 100644 --- a/src/Timeout.ts +++ b/src/Timeout.ts @@ -29,7 +29,6 @@ class Timeout { * Starts or restarts the timer * * @param {number} [newTime] - override time to call the callback - * @returns {Timeout} current instance */ public start(newTime?: number) { if (newTime != null) { @@ -37,30 +36,55 @@ class Timeout { this._time = newTime; } this.stop(); + this._start(); + } + + private _start() { if (this._time !== Infinity) { this._timerId = setTimeout(() => { this._cb(); this.stop(); }, this._time); } - return this; + } + + /** + * Starts the timer only if it's not already started + * @returns {boolean} - true if newly started, false if already started + */ + public startOnly() { + if (this._timerId !== null) { + return false; + } + this._start(); + return true; + } + + /** + * Restarts the timer only if it's already started + * @returns {boolean} - true if restarted, false if not started + */ + public restartOnly() { + if (this._timerId === null) { + return false; + } + this.stop(); + this._start(); + return true; } /** * Stops the timer, so callback won't be fired - * - * @returns {Timeout} current instance */ public stop() { - if (this._timerId) { + if (this._timerId !== null) { clearTimeout(this._timerId); this._timerId = null; } - return this; } public get started() { - return this._timerId != null; + return this._timerId !== null; } } diff --git a/tutorials/Usage.md b/tutorials/Usage.md index be9fc9e..ff08da0 100644 --- a/tutorials/Usage.md +++ b/tutorials/Usage.md @@ -52,6 +52,8 @@ timeout.start(); button.addEventListener('click', () => timeout.start(2000)); ``` +> Important: `startOnly` and `restartOnly` does not support changing timeout value. + ## Important difference from setTimeout and setInterval A `timer.start()` method has to be run in order to start the timeout/interval, as opposed to using native