Skip to content

Commit

Permalink
feat(collection): add Series methods
Browse files Browse the repository at this point in the history
  • Loading branch information
rafamel committed Dec 13, 2021
1 parent e34ca8d commit 87ca458
Show file tree
Hide file tree
Showing 4 changed files with 271 additions and 0 deletions.
87 changes: 87 additions & 0 deletions src/collection/Series.ts
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);
}
}
1 change: 1 addition & 0 deletions src/collection/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Series';
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
export * from './classes';
export * from './collection';

// TODO: upgrade to native AbortController
180 changes: 180 additions & 0 deletions test/collection/Series.test.ts
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);
});
});

0 comments on commit 87ca458

Please sign in to comment.