@timberio/tools
Advanced tools
Comparing version 0.3.0 to 0.4.0
"use strict"; | ||
var __importDefault = | ||
(this && this.__importDefault) || | ||
function(mod) { | ||
return mod && mod.__esModule ? mod : { default: mod }; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -12,2 +10,2 @@ const queue_1 = __importDefault(require("./queue")); | ||
exports.makeThrottle = throttle_1.default; | ||
//# sourceMappingURL=index.js.map | ||
//# sourceMappingURL=index.js.map |
@@ -6,23 +6,23 @@ /** | ||
export default class Queue<T> { | ||
/** | ||
* First node in the tree | ||
*/ | ||
private first?; | ||
/** | ||
* Last node in the tree | ||
*/ | ||
private last?; | ||
/** | ||
* Number of items in the queue | ||
*/ | ||
length: number; | ||
/** | ||
* Pushes a value into the queue. | ||
* @param value - Any object to push into the queue | ||
*/ | ||
push(value: any): void; | ||
/** | ||
* Remove a value from the start of the queue (FIFO) and return it | ||
*/ | ||
shift(): T | undefined; | ||
/** | ||
* First node in the tree | ||
*/ | ||
private first?; | ||
/** | ||
* Last node in the tree | ||
*/ | ||
private last?; | ||
/** | ||
* Number of items in the queue | ||
*/ | ||
length: number; | ||
/** | ||
* Pushes a value into the queue. | ||
* @param value - Any object to push into the queue | ||
*/ | ||
push(value: any): void; | ||
/** | ||
* Remove a value from the start of the queue (FIFO) and return it | ||
*/ | ||
shift(): T | undefined; | ||
} |
@@ -8,35 +8,35 @@ "use strict"; | ||
class Queue { | ||
constructor() { | ||
constructor() { | ||
/** | ||
* First node in the tree | ||
*/ | ||
/** | ||
* Number of items in the queue | ||
*/ | ||
this.length = 0; | ||
} | ||
/** | ||
* First node in the tree | ||
* Pushes a value into the queue. | ||
* @param value - Any object to push into the queue | ||
*/ | ||
push(value) { | ||
const node = { value }; | ||
this.last = this.last ? (this.last.next = node) : (this.first = node); | ||
this.length++; | ||
} | ||
/** | ||
* Number of items in the queue | ||
* Remove a value from the start of the queue (FIFO) and return it | ||
*/ | ||
this.length = 0; | ||
} | ||
/** | ||
* Pushes a value into the queue. | ||
* @param value - Any object to push into the queue | ||
*/ | ||
push(value) { | ||
const node = { value }; | ||
this.last = this.last ? (this.last.next = node) : (this.first = node); | ||
this.length++; | ||
} | ||
/** | ||
* Remove a value from the start of the queue (FIFO) and return it | ||
*/ | ||
shift() { | ||
if (this.first) { | ||
const { value } = this.first; | ||
this.first = this.first.next; | ||
if (!--this.length) { | ||
this.last = undefined; | ||
} | ||
return value; | ||
shift() { | ||
if (this.first) { | ||
const { value } = this.first; | ||
this.first = this.first.next; | ||
if (!--this.length) { | ||
this.last = undefined; | ||
} | ||
return value; | ||
} | ||
} | ||
} | ||
} | ||
exports.default = Queue; | ||
//# sourceMappingURL=queue.js.map | ||
//# sourceMappingURL=queue.js.map |
"use strict"; | ||
var __importDefault = | ||
(this && this.__importDefault) || | ||
function(mod) { | ||
return mod && mod.__esModule ? mod : { default: mod }; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const queue_1 = __importDefault(require("./queue")); | ||
describe("queue tests", () => { | ||
it("should store correct number of items in queue", () => { | ||
const q = new queue_1.default(); | ||
const numberOfItems = 10; | ||
for (let i = 0; i < numberOfItems; i++) { | ||
q.push(i); | ||
} | ||
expect(q.length).toEqual(numberOfItems); | ||
}); | ||
it("should retrieve the items in FIFO order", () => { | ||
const q = new queue_1.default(); | ||
const numberOfItems = 10; | ||
const list = []; | ||
// Insert items to both the array and queue | ||
for (let i = 0; i < numberOfItems; i++) { | ||
list.push(i); | ||
q.push(i); | ||
} | ||
// Map over plain array (which is ordered), and test | ||
// against the values from the queue | ||
list.forEach(item => { | ||
expect(q.shift()).toEqual(item); | ||
it("should store correct number of items in queue", () => { | ||
const q = new queue_1.default(); | ||
const numberOfItems = 10; | ||
for (let i = 0; i < numberOfItems; i++) { | ||
q.push(i); | ||
} | ||
expect(q.length).toEqual(numberOfItems); | ||
}); | ||
// Queue should now be empty | ||
expect(q.length).toEqual(0); | ||
}); | ||
it("should handle 100,000 items", () => { | ||
const q = new queue_1.default(); | ||
const numberOfItems = 100000; | ||
for (let i = 0; i < numberOfItems; i++) { | ||
q.push(i); | ||
} | ||
expect(q.length).toEqual(numberOfItems); | ||
}); | ||
it("should return undefined if queue is empty", () => { | ||
const q = new queue_1.default(); | ||
expect(q.shift()).toEqual(undefined); | ||
}); | ||
it("should retrieve the items in FIFO order", () => { | ||
const q = new queue_1.default(); | ||
const numberOfItems = 10; | ||
const list = []; | ||
// Insert items to both the array and queue | ||
for (let i = 0; i < numberOfItems; i++) { | ||
list.push(i); | ||
q.push(i); | ||
} | ||
// Map over plain array (which is ordered), and test | ||
// against the values from the queue | ||
list.forEach(item => { | ||
expect(q.shift()).toEqual(item); | ||
}); | ||
// Queue should now be empty | ||
expect(q.length).toEqual(0); | ||
}); | ||
it("should handle 100,000 items", () => { | ||
const q = new queue_1.default(); | ||
const numberOfItems = 100000; | ||
for (let i = 0; i < numberOfItems; i++) { | ||
q.push(i); | ||
} | ||
expect(q.length).toEqual(numberOfItems); | ||
}); | ||
it("should return undefined if queue is empty", () => { | ||
const q = new queue_1.default(); | ||
expect(q.shift()).toEqual(undefined); | ||
}); | ||
}); | ||
//# sourceMappingURL=queue.test.js.map | ||
//# sourceMappingURL=queue.test.js.map |
@@ -6,4 +6,2 @@ import { InferArgs } from "./types"; | ||
*/ | ||
export default function makeThrottle<T extends (...args: any[]) => any>( | ||
max: number | ||
): (fn: T) => (...args: InferArgs<T>[]) => Promise<InferArgs<T>>; | ||
export default function makeThrottle<T extends (...args: any[]) => any>(max: number): (fn: T) => (...args: InferArgs<T>[]) => Promise<InferArgs<T>>; |
"use strict"; | ||
var __importDefault = | ||
(this && this.__importDefault) || | ||
function(mod) { | ||
return mod && mod.__esModule ? mod : { default: mod }; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -14,49 +12,51 @@ const queue_1 = __importDefault(require("./queue")); | ||
function makeThrottle(max) { | ||
// Current iteration cycle | ||
let current = 0; | ||
// Create a FIFO queue | ||
const queue = new queue_1.default(); | ||
/** | ||
* Throttle function that throttles the passed func according to `max` | ||
* @param fn - async function to resolve | ||
*/ | ||
function throttle(fn) { | ||
return async (...args) => { | ||
return new Promise((resolve, reject) => { | ||
/** | ||
* Handler for resolving the Promise chain | ||
*/ | ||
async function handler() { | ||
// Only resolve if the `max` hasn't been exhausted | ||
if (current < max) { | ||
// Increment the available slot size | ||
current++; | ||
try { | ||
// Await the passed function here first, to determine if any | ||
// errors are thrown, so they can be handled by our outside `reject` | ||
resolve(await fn(...args)); | ||
} catch (e) { | ||
reject(e); | ||
} | ||
// Since this has now resolved, make the slot available again | ||
current--; | ||
// If there are items waiting in the queue, resolve the next | ||
// Promise | ||
if (queue.length > 0) { | ||
queue.shift()(); | ||
} | ||
} else { | ||
// The `max` has been exceeded - push onto the queue to wait | ||
queue.push(handler); | ||
} | ||
} | ||
// Return the async handler | ||
return handler(); | ||
}); | ||
}; | ||
} | ||
// Return the throttle function | ||
return throttle; | ||
// Current iteration cycle | ||
let current = 0; | ||
// Create a FIFO queue | ||
const queue = new queue_1.default(); | ||
/** | ||
* Throttle function that throttles the passed func according to `max` | ||
* @param fn - async function to resolve | ||
*/ | ||
function throttle(fn) { | ||
return async (...args) => { | ||
return new Promise((resolve, reject) => { | ||
/** | ||
* Handler for resolving the Promise chain | ||
*/ | ||
async function handler() { | ||
// Only resolve if the `max` hasn't been exhausted | ||
if (current < max) { | ||
// Increment the available slot size | ||
current++; | ||
try { | ||
// Await the passed function here first, to determine if any | ||
// errors are thrown, so they can be handled by our outside `reject` | ||
resolve(await fn(...args)); | ||
} | ||
catch (e) { | ||
reject(e); | ||
} | ||
// Since this has now resolved, make the slot available again | ||
current--; | ||
// If there are items waiting in the queue, resolve the next | ||
// Promise | ||
if (queue.length > 0) { | ||
queue.shift()(); | ||
} | ||
} | ||
else { | ||
// The `max` has been exceeded - push onto the queue to wait | ||
queue.push(handler); | ||
} | ||
} | ||
// Return the async handler | ||
return handler(); | ||
}); | ||
}; | ||
} | ||
// Return the throttle function | ||
return throttle; | ||
} | ||
exports.default = makeThrottle; | ||
//# sourceMappingURL=throttle.js.map | ||
//# sourceMappingURL=throttle.js.map |
"use strict"; | ||
var __importDefault = | ||
(this && this.__importDefault) || | ||
function(mod) { | ||
return mod && mod.__esModule ? mod : { default: mod }; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const throttle_1 = __importDefault(require("./throttle")); | ||
describe("Throttle tests", () => { | ||
it("should throttle Promises", async () => { | ||
// Fixtures | ||
const max = 2; | ||
const throttleTime = 20; // ms | ||
const numberOfPromises = 10; | ||
// Create the throttle function | ||
const throttle = throttle_1.default(max); | ||
// Create the pipeline function to use the throttle | ||
const pipeline = throttle( | ||
async log => | ||
new Promise(resolve => { | ||
setTimeout(() => { | ||
resolve(log); | ||
}, throttleTime); | ||
}) | ||
); | ||
// Build the promises | ||
const promises = []; | ||
// Start the timer | ||
const start = process.hrtime(); | ||
for (let i = 0; i < numberOfPromises; i++) { | ||
promises.push(pipeline({ message: "Hey" })); | ||
} | ||
// Await until they've all finished | ||
await Promise.all(promises); | ||
// End the timer | ||
const end = process.hrtime(start)[1] / 1000000; | ||
// Expect time to have taken (numberOfPromises / max) * throttleTime | ||
const expectedTime = (numberOfPromises / max) * throttleTime; | ||
expect(end).toBeGreaterThanOrEqual(expectedTime); | ||
}); | ||
it("should handle rejections", async () => { | ||
// Fixtures | ||
const numberOfPromises = 10; | ||
// Create the throttle function | ||
const throttle = throttle_1.default(5); | ||
// Error counter | ||
let errors = 0; | ||
// Create a throttled function that will throw half the time | ||
const pipeline = throttle(async i => { | ||
if (i % 2 == 0) { | ||
throw new Error("Thrown inside throttled function!"); | ||
} | ||
return i; | ||
it("should throttle Promises", async () => { | ||
// Fixtures | ||
const max = 2; | ||
const throttleTime = 20; // ms | ||
const numberOfPromises = 10; | ||
// Create the throttle function | ||
const throttle = throttle_1.default(max); | ||
// Create the pipeline function to use the throttle | ||
const pipeline = throttle(async (log) => new Promise(resolve => { | ||
setTimeout(() => { | ||
resolve(log); | ||
}, throttleTime); | ||
})); | ||
// Build the promises | ||
const promises = []; | ||
// Start the timer | ||
const start = process.hrtime(); | ||
for (let i = 0; i < numberOfPromises; i++) { | ||
promises.push(pipeline({ message: "Hey" })); | ||
} | ||
// Await until they've all finished | ||
await Promise.all(promises); | ||
// End the timer | ||
const end = process.hrtime(start)[1] / 1000000; | ||
// Expect time to have taken (numberOfPromises / max) * throttleTime | ||
const expectedTime = (numberOfPromises / max) * throttleTime; | ||
expect(end).toBeGreaterThanOrEqual(expectedTime); | ||
}); | ||
for (let i = 0; i < numberOfPromises; i++) { | ||
await pipeline(i).catch(() => errors++); | ||
} | ||
expect(errors).toEqual(numberOfPromises / 2); | ||
}); | ||
it("should handle rejections", async () => { | ||
// Fixtures | ||
const numberOfPromises = 10; | ||
// Create the throttle function | ||
const throttle = throttle_1.default(5); | ||
// Error counter | ||
let errors = 0; | ||
// Create a throttled function that will throw half the time | ||
const pipeline = throttle(async (i) => { | ||
if (i % 2 == 0) { | ||
throw new Error("Thrown inside throttled function!"); | ||
} | ||
return i; | ||
}); | ||
for (let i = 0; i < numberOfPromises; i++) { | ||
await pipeline(i).catch(() => errors++); | ||
} | ||
expect(errors).toEqual(numberOfPromises / 2); | ||
}); | ||
}); | ||
//# sourceMappingURL=throttle.test.js.map | ||
//# sourceMappingURL=throttle.test.js.map |
@@ -5,10 +5,10 @@ /** | ||
export interface IQueue<T> { | ||
/** | ||
* Value of queue should be an object | ||
*/ | ||
value: T; | ||
/** | ||
* Leaf node in queue, representing the next value | ||
*/ | ||
next?: IQueue<T>; | ||
/** | ||
* Value of queue should be an object | ||
*/ | ||
value: T; | ||
/** | ||
* Leaf node in queue, representing the next value | ||
*/ | ||
next?: IQueue<T>; | ||
} | ||
@@ -18,6 +18,2 @@ /** | ||
*/ | ||
export declare type InferArgs<T> = T extends ( | ||
...args: any[] | ||
) => Promise<infer U> | ||
? U | ||
: void; | ||
export declare type InferArgs<T> = T extends (...args: any[]) => Promise<infer U> ? U : void; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
//# sourceMappingURL=types.js.map | ||
//# sourceMappingURL=types.js.map |
{ | ||
"name": "@timberio/tools", | ||
"version": "0.3.0", | ||
"version": "0.4.0", | ||
"description": "Javascript logging tools", | ||
@@ -10,3 +10,5 @@ "main": "dist/cjs/index.js", | ||
"scripts": { | ||
"build": "tsc && tsc -p tsconfig.es6.json", | ||
"build:cjs": "tsc", | ||
"build:es6": "tsc -p tsconfig.es6.json", | ||
"build": "run-p build:*", | ||
"prepublish": "npm run build", | ||
@@ -29,2 +31,3 @@ "test": "jest" | ||
"jest": "^23.6.0", | ||
"npm-run-all": "^4.1.3", | ||
"prettier": "^1.15.2", | ||
@@ -31,0 +34,0 @@ "ts-jest": "^23.10.4", |
@@ -7,4 +7,32 @@ # 🌲 Timber - Javascript tools | ||
### `makeThrottle<TFunc>(max: number)` | ||
### `Queue<T>` | ||
Generic [FIFO](<https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)>) queue. Used by `makeThrottle` to store pipeline functions to be executed as concurrent 'slots' become available. Provides fast retrieval for any primitive or object that needs ordered, first-in, first-out retrieval. | ||
**Usage example** | ||
```typescript | ||
import { Queue } from "@timberio/tools"; | ||
// Interface representing a person | ||
interface IPerson { | ||
name: string; | ||
age: number; | ||
} | ||
// Create a queue to store `IPerson` objects | ||
const q = new Queue<IPerson>(); | ||
// Add a couple of records... | ||
q.push({ name: "Jeff", age: 50 }); | ||
q.push({ name: "Sally", age: 39 }); | ||
// Pull values from the queue... | ||
while (q.length) { | ||
console.log(q.shift().name); // <-- first Jeff, then Sally... | ||
} | ||
``` | ||
### `makeThrottle<T>(max: number)` | ||
Returns a `throttle` higher-order function, which wraps an `async` function, and limits the number of active Promises to `max: number` | ||
@@ -15,3 +43,3 @@ | ||
``` | ||
throttle(fn: TFunc): (...args: TFuncArg[]) => Promise<TFuncArg> | ||
throttle(fn: T): (...args: InferArgs<T>[]) => Promise<InferArgs<T>> | ||
``` | ||
@@ -18,0 +46,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
49263
76
7
916