diff --git a/README.md b/README.md index b1eca20..d27c68f 100644 --- a/README.md +++ b/README.md @@ -5,49 +5,135 @@ # Usage ```javascript -const { ObjectPool } = require("@jacekpietal/object-pool"); - -const pool = new ObjectPool(factory); +const ObjectPool = require("@jacekpietal/object-pool"); +``` -// you should provide this function -function factory() { - // that returns a new object instance -} +```javascript +const pool = new ObjectPool( + // you should provide this function + function factory() { + // that returns a new object instance + // can be async + } +); // will return value from pool // if pool empty uses factory function -const nextValue = pool.next(); +const object = pool.get(); // returns value to the pool -pool.back(nextValue); +pool.put(object); + +// prints 1 +console.log(pool.size); + +// removes value from pool +pool.delete(object); + +// prints 0 +console.log(pool.size); + +// pre-fills pool up to size +// useful I think only for async functions +pool.size = 100; + +// empties pool +pool.size = 0; + +// size manipulations do trigger as required +// `put` and `delete` events for each change +``` + +## With Promises / async functions + +If you want to use async functions be sure to put the same thing you get from the pool. + +```javascript +const pool = new ObjectPool(async () => "value"); + +async function test() { + const once = pool.get(); + const value1 = await once; + + // put back the promise reference + pool.put(once); + + const again = pool.get(); + const value2 = await again; + + // prints: + // { + // once: Promise { 'value' }, + // again: Promise { 'value' }, + // value1: 'value', + // value2: 'value' + // } + console.log({ once, again, value1, value2 }); +} + +test(); ``` # Events ```javascript -pool.events.on("next", (value) => { - console.log({ next: value }); +// all actions are accompanied by EventEmitter events + +pool.events.on("get", (value) => { + console.log({ get: value }); }); -pool.events.on("back", (value) => { - console.log({ back: value }); +pool.events.on("put", (value) => { + console.log({ put: value }); }); -pool.events.on("remove", (value) => { - console.log({ remove: value }); +pool.events.on("delete", (value) => { + console.log({ delete: value }); }); ``` # API ```javascript -pool.next(); // returns next value from pool +pool.get(); // returns get value from pool -pool.back(object); // puts back value to end of pool +pool.put(object); // puts put value to end of pool -pool.empty(); // empties the whole pool +pool.delete(object); // deletes object from pool manually +``` -pool.remove(object); // removes object from pool manually +# Tests + +Please check tests for more complicated cases: + +```bash +$ yarn test +yarn run v1.22.5 +$ jest + PASS ./index.spec.js + GIVEN ObjectPool instance + ✓ THEN It should create (2 ms) + WHEN pool.get is called + ✓ THEN even if pool.size is 0 (1 ms) + ✓ THEN It should return new instance of factory call + ✓ THEN events.get should be emitted (1 ms) + WHEN pool.put is called + ✓ THEN pool.size should be greater than 0 afterwards + ✓ THEN events.put should be emitted (1 ms) + WHEN promise is `put` and then `get` again + ✓ THEN it still works as expected (1502 ms) + WHEN pool.size is set to 0 on non-empty pool + ✓ THEN events.delete should be emitted for each item (1 ms) + WHEN pool.size is set + ✓ THEN pool size adjusts and is being filled accordingly (2 ms) + ✓ THEN pool size adjusts and is being trimmed accordingly (1 ms) + +Test Suites: 1 passed, 1 total +Tests: 10 passed, 10 total +Snapshots: 0 total +Time: 2.668 s +Ran all test suites. +Done in 3.41s. ``` # License diff --git a/index.js b/index.js index 16de02f..29c5f61 100644 --- a/index.js +++ b/index.js @@ -3,53 +3,71 @@ const EventEmitter = require("events"); class ObjectPool { - constructor(factory) { - this.factory = factory; + constructor(factoryFunction) { + this.factory = factoryFunction; + this.objects = new Set(); + this.events = new EventEmitter(); } - empty() { - if (this.objects.size === 0) { - return; + get size() { + return this.objects.size; + } + + set size(size = 0) { + if (typeof size !== "number") { + throw new Error("Parameter is not a number: " + typeof size); } - const [result] = this.objects.values(); + let current = this.objects.size; - this.remove(result); + while (current < size) { + const object = this.factory(); - this.empty(); - } + this.put(object); + + current++; + } - next() { - if (this.objects.size === 0) { - const result = this.factory(); + while (current > size) { + const [object] = this.objects.values(); - this.events.emit("next", result); + this.delete(object); - return result; + current--; } + } - const [result] = this.objects.values(); + get() { + if (this.size === 0) { + const object = this.factory(); - this.objects.delete(result); + this.events.emit("get", object); - this.events.emit("next", result); + return object; + } - return result; - } + const [object] = this.objects.values(); - remove(object) { this.objects.delete(object); - this.events.emit("remove", object); + this.events.emit("get", object); + + return object; } - back(object) { + put(object) { this.objects.add(object); - this.events.emit("back", object); + this.events.emit("put", object); + } + + delete(object) { + this.objects.delete(object); + + this.events.emit("delete", object); } } -module.exports.ObjectPool = ObjectPool; +module.exports = ObjectPool; diff --git a/index.spec.js b/index.spec.js index dd61143..d3c09cb 100644 --- a/index.spec.js +++ b/index.spec.js @@ -1,64 +1,118 @@ -const { ObjectPool } = require("."); +"use strict"; + +const ObjectPool = require("."); describe("GIVEN ObjectPool instance", () => { let pool; + const resolveArgument = "[Promise resolved]"; + + function factory() { + return new Promise((resolve) => { + setTimeout(() => resolve(resolveArgument), 500); + }); + } + beforeEach(() => { - pool = new ObjectPool(() => true); + pool = new ObjectPool(factory); }); it("THEN It should create", () => { expect(pool).toBeTruthy(); }); - describe("WHEN pool.next is called", () => { - it("THEN even if objects size is 0", () => { - expect(pool.objects.size).toBe(0); + describe("WHEN pool.get is called", () => { + it("THEN even if pool.size is 0", () => { + expect(pool.size).toBe(0); }); - it("THEN It should return new instance of object", () => { - expect(pool.next()).toBe(true); + it("THEN It should return new instance of factory call", () => { + expect(typeof pool.get()).toBe(typeof factory()); }); - it("THEN events.next should be emitted", async (done) => { - pool.events.on("next", () => done()); + it("THEN events.get should be emitted", async (done) => { + pool.events.on("get", () => done()); - pool.next(); + pool.get(); }); }); - describe("WHEN pool.back is called", () => { - it("THEN objects size should be greater than 0 afterwards", () => { - pool.back(false); + describe("WHEN pool.put is called", () => { + it("THEN pool.size should be greater than 0 afterwards", () => { + pool.put(false); + + expect(pool.size).toBeGreaterThan(0); + }); + + it("THEN events.put should be emitted", async (done) => { + pool.events.on("put", () => done()); - expect(pool.objects.size).toBeGreaterThan(0); + pool.put(null); }); + }); + + describe("WHEN promise is `put` and then `get` again", () => { + it("THEN it still works as expected", async (done) => { + const once = pool.get(); + const value = await once; + + setTimeout(() => { + pool.put(once); + + const again = pool.get(); + + expect(typeof again).toBe(typeof once); + expect(typeof value).toBe(typeof resolveArgument); + + expect(value).toBe(resolveArgument); - it("THEN events.back should be emitted", async (done) => { - pool.events.on("back", () => done()); + expect(Promise.resolve(once)).toBe(once); + expect(Promise.resolve(again)).toBe(again); - pool.back(null); + done(); + }, 1000); }); }); - describe("WHEN pool.empty is called on non-empty pool", () => { + describe("WHEN pool.size is set to 0 on non-empty pool", () => { // prepare with data beforeEach(() => { - pool.back(null); - pool.back(null); - pool.back(null); + pool.put(null); + pool.put(null); + pool.put(null); }); - it("THEN events.remove should be emitted for each item", async (done) => { - let count = pool.objects.size; + it("THEN events.delete should be emitted for each item", async (done) => { + let count = pool.size; - pool.events.on("remove", () => { + pool.events.on("delete", () => { if (!--count) { - done() + done(); } }); - pool.empty(); + pool.size = 0; + }); + }); + + describe("WHEN pool.size is set", () => { + const initialSize = 5; + + // prepare with data + beforeEach(() => { + pool.size = initialSize; + }); + + it("THEN pool size adjusts and is being filled accordingly", () => { + pool.size = initialSize * 10; + + expect(pool.size).toBe(initialSize * 10); + }); + + it("THEN pool size adjusts and is being trimmed accordingly", () => { + pool.size = 1; + + expect(pool.size).toBe(1); }); }); }); diff --git a/package.json b/package.json index 77c7d18..fc1e906 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@jacekpietal/object-pool", - "version": "1.0.2", - "description": "Object Pool manager for instantiating, getting, reuising and destroying objects in javascript (mostly for games)", + "version": "1.1.0", + "description": "zero-dependency Object Pool manager for instantiating, getting, reuising and destroying objects in javascript", "main": "index.js", "scripts": { "test": "jest"