-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(collection): add Series methods
- Loading branch information
Showing
4 changed files
with
271 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
export class Series { | ||
/** | ||
* Maps an iterable by running the callback | ||
* function in series. | ||
*/ | ||
public static async map<T, U>( | ||
iterable: Iterable<PromiseLike<T> | T>, | ||
fn: ( | ||
value: T, | ||
index: number, | ||
arr: Array<PromiseLike<T> | T> | ||
) => PromiseLike<U> | U | ||
): Promise<U[]> { | ||
const arr = Array.from(iterable); | ||
const response: U[] = []; | ||
|
||
for (let i = 0; i < arr.length; i++) { | ||
response.push(await fn(await arr[i], i, arr)); | ||
} | ||
|
||
return response; | ||
} | ||
/** | ||
* Filters an iterable by running the callback | ||
* function in series. | ||
*/ | ||
public static async filter<T>( | ||
iterable: Iterable<PromiseLike<T> | T>, | ||
fn: ( | ||
value: T, | ||
index: number, | ||
arr: Array<PromiseLike<T> | T> | ||
) => PromiseLike<boolean> | boolean | ||
): Promise<T[]> { | ||
const arr = Array.from(iterable); | ||
const response: T[] = []; | ||
|
||
for (let i = 0; i < arr.length; i++) { | ||
const value = await arr[i]; | ||
const select = await fn(value, i, arr); | ||
if (select) response.push(value); | ||
} | ||
|
||
return response; | ||
} | ||
/** | ||
* Reduces an iterable by running the callback | ||
* function in series. | ||
*/ | ||
public static async reduce<T, U = T>( | ||
iterable: Iterable<PromiseLike<T> | T>, | ||
fn: ( | ||
accumulator: U, | ||
value: T, | ||
index: number, | ||
arr: Array<PromiseLike<T> | T> | ||
) => PromiseLike<U> | U, | ||
initialValue?: PromiseLike<U> | U | ||
): Promise<U> { | ||
const arr = Array.from(iterable); | ||
|
||
let response: U = | ||
initialValue === undefined | ||
? ((await arr[0]) as any as U) | ||
: await fn(await initialValue, await arr[0], 0, arr); | ||
|
||
for (let i = 1; i < arr.length; i++) { | ||
response = await fn(response, await arr[i], i, arr); | ||
} | ||
|
||
return response; | ||
} | ||
/** | ||
* Runs a callback for each item of | ||
* an Iterable in series. | ||
*/ | ||
public static async each<T>( | ||
iterable: Iterable<PromiseLike<T> | T>, | ||
fn: ( | ||
value: T, | ||
index: number, | ||
arr: Array<PromiseLike<T> | T> | ||
) => PromiseLike<void> | void | ||
): Promise<void> { | ||
await Series.map(iterable, fn); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './Series'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,4 @@ | ||
export * from './classes'; | ||
export * from './collection'; | ||
|
||
// TODO: upgrade to native AbortController |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
import { test, describe, expect } from '@jest/globals'; | ||
import { Series } from '../../src/collection'; | ||
import { LazyPromise } from '../../src/classes'; | ||
|
||
const createArr = (arr: number[] = [1, 2, 3, 4]): Array<Promise<number>> => { | ||
return arr.map((x) => Promise.resolve(x)); | ||
}; | ||
const createDelayedArr = ( | ||
n: number = 50, | ||
ms: number = 1 | ||
): Array<Promise<number>> => { | ||
return Array(50) | ||
.fill(0) | ||
.map((_, i) => { | ||
return new LazyPromise((resolve) => { | ||
setTimeout(() => resolve(i), ms * (n - i)); | ||
}); | ||
}); | ||
}; | ||
|
||
describe(`Series.map`, () => { | ||
test(`maps`, async () => { | ||
const p = Series.map(createArr(), (x) => x + 1); | ||
await expect(p).resolves.toEqual([2, 3, 4, 5]); | ||
}); | ||
test(`maps for async inner fn`, async () => { | ||
const p = Series.map(createArr(), async (x) => x + 1); | ||
await expect(p).resolves.toEqual([2, 3, 4, 5]); | ||
}); | ||
test(`receives all args`, async () => { | ||
expect.assertions(9); | ||
|
||
const pArr = createArr(); | ||
const p = Series.map(pArr, (x, i, arr) => { | ||
expect(x).toBe(i + 1); | ||
expect(arr).toEqual(pArr); | ||
return x; | ||
}); | ||
await expect(p).resolves.toEqual([1, 2, 3, 4]); | ||
}); | ||
test(`runs in series`, async () => { | ||
const arr = createDelayedArr(); | ||
const init = Date.now(); | ||
const p = Series.map(arr, async (x) => x + 1); | ||
await expect(p).resolves.toEqual(arr.map((_, i) => i + 1)); | ||
expect(Date.now() - init).toBeGreaterThan(1200); | ||
}); | ||
}); | ||
|
||
describe(`Series.filter`, () => { | ||
test(`filters`, async () => { | ||
const p = Series.filter(createArr(), (x) => x % 2 === 0); | ||
await expect(p).resolves.toEqual([2, 4]); | ||
}); | ||
test(`filters for async inner fn`, async () => { | ||
const p = Series.filter(createArr(), async (x) => x % 2 === 0); | ||
await expect(p).resolves.toEqual([2, 4]); | ||
}); | ||
test(`receives all args`, async () => { | ||
expect.assertions(9); | ||
|
||
const pArr = createArr(); | ||
const p = Series.filter(pArr, (x, i, arr) => { | ||
expect(x).toBe(i + 1); | ||
expect(arr).toEqual(pArr); | ||
return x % 2 === 0; | ||
}); | ||
await expect(p).resolves.toEqual([2, 4]); | ||
}); | ||
test(`runs in series`, async () => { | ||
const arr = createDelayedArr(); | ||
const init = Date.now(); | ||
const p = Series.filter(arr, async (x) => x % 2 === 0); | ||
await expect(p).resolves.toEqual( | ||
arr.map((_, i) => i).filter((x) => x % 2 === 0) | ||
); | ||
expect(Date.now() - init).toBeGreaterThan(1200); | ||
}); | ||
}); | ||
|
||
describe(`Series.reduce`, () => { | ||
test(`reduces`, async () => { | ||
const p = Series.reduce(createArr(), (acc, x) => acc + x); | ||
await expect(p).resolves.toEqual(10); | ||
}); | ||
test(`reduces w/ initialValue`, async () => { | ||
const p = Series.reduce(createArr(), (acc, x) => acc + x, 4); | ||
await expect(p).resolves.toEqual(14); | ||
}); | ||
test(`reduces w/ initialValue as Promise`, async () => { | ||
const p = Series.reduce( | ||
createArr(), | ||
(acc, x) => acc + x, | ||
Promise.resolve(4) | ||
); | ||
await expect(p).resolves.toEqual(14); | ||
}); | ||
test(`reduces for async inner fn`, async () => { | ||
const p = Series.reduce(createArr(), async (acc, x) => x + acc); | ||
await expect(p).resolves.toEqual(10); | ||
}); | ||
test(`receives all args`, async () => { | ||
expect.assertions(13); | ||
|
||
const pArr = createArr(); | ||
const p = Series.reduce( | ||
pArr, | ||
(acc, x, i, arr) => { | ||
expect(x).toBe(i + 1); | ||
expect(arr).toEqual(pArr); | ||
expect(acc).toBe( | ||
Array(i + 1) | ||
.fill(0) | ||
.map((_, i) => i) | ||
.reduce((acc, x) => acc + x) | ||
); | ||
return acc + x; | ||
}, | ||
0 | ||
); | ||
await expect(p).resolves.toEqual(10); | ||
}); | ||
test(`runs in series`, async () => { | ||
const arr = createDelayedArr(); | ||
const init = Date.now(); | ||
const p = Series.reduce(arr, async (acc, x) => acc + x); | ||
await expect(p).resolves.toEqual( | ||
arr.map((_, i) => i).reduce((acc, x) => acc + x) | ||
); | ||
expect(Date.now() - init).toBeGreaterThan(1200); | ||
}); | ||
}); | ||
|
||
describe(`Series.each`, () => { | ||
test(`runs for each`, async () => { | ||
const resArr: any[] = []; | ||
const p = Series.each(createArr(), (x) => { | ||
resArr.push(x); | ||
}); | ||
|
||
await expect(p).resolves.toBe(undefined); | ||
expect(resArr).toEqual([1, 2, 3, 4]); | ||
}); | ||
test(`run on each for async inner fn`, async () => { | ||
expect.assertions(6); | ||
|
||
let isDone = false; | ||
const resArr: any[] = []; | ||
const p = Series.each(createArr(), async (x) => { | ||
expect(isDone).toBe(false); | ||
resArr.push(x); | ||
}); | ||
|
||
await expect(p).resolves.toBe(undefined); | ||
expect(resArr).toEqual([1, 2, 3, 4]); | ||
isDone = true; | ||
}); | ||
test(`receives all args`, async () => { | ||
expect.assertions(10); | ||
|
||
const resArr: any[] = []; | ||
const pArr = createArr(); | ||
const p = Series.each(pArr, (x, i, arr) => { | ||
expect(x).toBe(i + 1); | ||
expect(arr).toEqual(pArr); | ||
resArr.push(x); | ||
}); | ||
|
||
await expect(p).resolves.toBe(undefined); | ||
expect(resArr).toEqual([1, 2, 3, 4]); | ||
}); | ||
test(`runs in series`, async () => { | ||
const arr = createDelayedArr(); | ||
const init = Date.now(); | ||
const p = Series.each(arr, async () => undefined); | ||
|
||
await p; | ||
expect(Date.now() - init).toBeGreaterThan(1200); | ||
}); | ||
}); |