Skip to content

Commit

Permalink
Refactor AsyncIterator utils, remove useless reduceAsyncIterator (#84)
Browse files Browse the repository at this point in the history
  • Loading branch information
BurnedMarshal authored Jul 31, 2020
1 parent b53ebe5 commit 8ffd883
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 108 deletions.
56 changes: 14 additions & 42 deletions src/utils/__tests__/async.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { isRight, left, right, Right } from "fp-ts/lib/Either";
import {
filterAsyncIterator,
flattenAsyncIterator,
reduceAsyncIterator
} from "../async";
import { filterAsyncIterator, flattenAsyncIterator } from "../async";

const mockNext = jest.fn();
const mockAsyncIterator = {
Expand Down Expand Up @@ -68,12 +64,13 @@ describe("flattenAsyncIterator utils", () => {
});
});

describe("mapEitherAsyncIterator utils", () => {
describe("filterAsyncIterator utils", () => {
beforeEach(() => {
jest.clearAllMocks();
});
it("should filter values that match the predicate", async () => {
const expectedRightValue = right(1);
const expectedReturnValue = true;
mockNext.mockImplementationOnce(async () => ({
done: false,
value: left(new Error("Left value error"))
Expand All @@ -84,7 +81,7 @@ describe("mapEitherAsyncIterator utils", () => {
}));
mockNext.mockImplementationOnce(async () => ({
done: true,
value: undefined
value: expectedReturnValue
}));
const iter = filterAsyncIterator<Right<Error, number>>(
mockAsyncIterator,
Expand All @@ -94,12 +91,16 @@ describe("mapEitherAsyncIterator utils", () => {
done: false,
value: expectedRightValue
});
expect(await iter.next()).toEqual({ done: true, value: undefined });
expect(await iter.next()).toEqual({
done: true,
value: expectedReturnValue
});
expect(mockNext).toBeCalledTimes(3);
});

it("should skip all values if don't match the predicate", async () => {
const leftValue = left(new Error("Left value error"));
const expectedReturnValue = true;
mockNext.mockImplementationOnce(async () => ({
done: false,
value: leftValue
Expand All @@ -110,45 +111,16 @@ describe("mapEitherAsyncIterator utils", () => {
}));
mockNext.mockImplementationOnce(async () => ({
done: true,
value: undefined
value: expectedReturnValue
}));
const iter = filterAsyncIterator<Right<Error, number>>(
mockAsyncIterator,
isRight
);
expect(await iter.next()).toEqual({ done: true, value: undefined });
expect(mockNext).toBeCalledTimes(3);
});
});

describe("reduceAsyncIterator", () => {
beforeEach(() => {
jest.clearAllMocks();
});
it("should reduce the documents of the wrapped iterator", async () => {
mockNext.mockImplementationOnce(async () => ({
done: false,
value: ["1", "2"]
}));
mockNext.mockImplementationOnce(async () => ({
done: false,
value: ["3", "4"]
}));
mockNext.mockImplementationOnce(async () => ({
expect(await iter.next()).toEqual({
done: true,
value: undefined
}));

const iter = reduceAsyncIterator(
mockAsyncIterator,
(prev: string, cur: string) => prev + cur,
""
);

const result1 = await iter.next();
expect(result1).toEqual({ done: false, value: "12" });
const result2 = await iter.next();
expect(result2).toEqual({ done: false, value: "34" });
expect(await iter.next()).toEqual({ done: true, value: undefined });
value: expectedReturnValue
});
expect(mockNext).toBeCalledTimes(3);
});
});
127 changes: 61 additions & 66 deletions src/utils/async.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,70 +50,62 @@ export async function asyncIterableToArray<T>(
}

/**
* Create a new AsyncIterable providing only the values that satisfy the predicate function.
* Create a new AsyncIterator providing only the values that satisfy the predicate function.
* The predicate function is also an optional Type Guard function if types T and K are different.
*
* Example:
* ```
* const i: AsyncIterable<Either<E, A>> = {} as AsyncIterable<Either<E, A>>;
* const f: AsyncIterable<Right<E, A>> = filterAsyncIterable<Either<E, A>, Right<E, A>>(i, isRight);
* const i: AsyncIterator<Either<E, A>> = {} as AsyncIterator<Either<E, A>>;
* const newI: AsyncIterator<Right<E, A>> = filterAsyncIterator<Either<E, A>, Right<E, A>>(i, isRight);
* ```
* @param iterable Original AsyncIterable
* @param iter Original AsyncIterator
* @param predicate Predicate function
*/
export const filterAsyncIterable = <T, K = T>(
iterable: AsyncIterable<T | K>,
export function filterAsyncIterator<T, K = T>(
iter: AsyncIterator<T | K>,
predicate: (value: T | K) => value is K
): AsyncIterable<K> => ({
async *[Symbol.asyncIterator](): AsyncIterator<K> {
// tslint:disable-next-line: await-promise
for await (const value of iterable) {
// tslint:disable-next-line: no-any
): AsyncIterator<K, any> {
// tslint:disable-next-line: no-any
async function* getValues(): AsyncGenerator<K, any, unknown> {
while (true) {
const { done, value } = await iter.next();
if (done) {
return value;
}
if (predicate(value)) {
yield value;
}
}
}
});
return {
next: async () => {
return await getValues().next();
}
};
}

/**
* Create a new AsyncIterator providing only the values that satisfy the predicate function.
* Create a new AsyncIterable providing only the values that satisfy the predicate function.
* The predicate function is also an optional Type Guard function if types T and K are different.
*
* Example:
* ```
* const i: AsyncIterator<Either<E, A>> = {} as AsyncIterator<Either<E, A>>;
* const newI: AsyncIterator<Right<E, A>> = filterAsyncIterator<Either<E, A>, Right<E, A>>(iterable, isRight);
* const i: AsyncIterable<Either<E, A>> = {} as AsyncIterable<Either<E, A>>;
* const f: AsyncIterable<Right<E, A>> = filterAsyncIterable<Either<E, A>, Right<E, A>>(i, isRight);
* ```
* @param iter Original AsyncIterator
* @param iterable Original AsyncIterable
* @param predicate Predicate function
*/
export function filterAsyncIterator<T, K = T>(
iter: AsyncIterator<T | K>,
export const filterAsyncIterable = <T, K = T>(
source: AsyncIterable<T | K>,
predicate: (value: T | K) => value is K
): AsyncIterator<K> {
const iterable = {
[Symbol.asyncIterator]: () => iter
): AsyncIterable<K> => {
const iter = source[Symbol.asyncIterator]();
return {
[Symbol.asyncIterator]: () => filterAsyncIterator(iter, predicate)
};
return filterAsyncIterable(iterable, predicate)[Symbol.asyncIterator]();
}

/**
* Create a new AsyncIterable which provide one by one the values ​​contained into the input AsyncIterable
*
* @param iterable Original AsyncIterable
*/
export const flattenAsyncIterable = <T>(
iterable: AsyncIterable<ReadonlyArray<T>>
): AsyncIterable<T> => ({
async *[Symbol.asyncIterator](): AsyncIterator<T> {
// tslint:disable-next-line: await-promise
for await (const value of iterable) {
for (const item of value) {
yield item;
}
}
}
});
};

/**
* Create a new AsyncIterator which provide one by one the values ​​contained into the input AsyncIterator
Expand All @@ -122,35 +114,38 @@ export const flattenAsyncIterable = <T>(
*/
export function flattenAsyncIterator<T>(
iter: AsyncIterator<ReadonlyArray<T>>
): AsyncIterator<T> {
const iterable = {
[Symbol.asyncIterator]: () => iter
};
return flattenAsyncIterable(iterable)[Symbol.asyncIterator]();
}

export function reduceAsyncIterable<A, B>(
iterable: AsyncIterable<ReadonlyArray<A>>,
reducer: (previousValue: B, currentValue: A) => B,
init: B
): AsyncIterable<B> {
return {
async *[Symbol.asyncIterator](): AsyncIterator<B> {
// tslint:disable-next-line: await-promise
for await (const value of iterable) {
yield value.reduce<B>(reducer, init);
// tslint:disable-next-line: no-any
): AsyncIterator<T, any> {
// tslint:disable-next-line: no-let readonly-array
let array: T[] = [];
// tslint:disable-next-line: no-any
async function* getValues(): AsyncGenerator<T, any, unknown> {
while (array.length === 0) {
const { done, value } = await iter.next();
if (done) {
return value;
}
array = Array.from(value);
}
yield array.shift() as T;
}
return {
next: async () => {
return await getValues().next();
}
};
}

export function reduceAsyncIterator<A, B>(
i: AsyncIterator<ReadonlyArray<A>>,
reducer: (previousValue: B, currentValue: A) => B,
init: B
): AsyncIterator<B> {
const iterable = {
[Symbol.asyncIterator]: () => i
/**
* Create a new AsyncIterable which provide one by one the values ​​contained into the input AsyncIterable
*
* @param source Original AsyncIterable
*/
export const flattenAsyncIterable = <T>(
source: AsyncIterable<ReadonlyArray<T>>
): AsyncIterable<T> => {
const iter = source[Symbol.asyncIterator]();
return {
[Symbol.asyncIterator]: () => flattenAsyncIterator(iter)
};
return reduceAsyncIterable(iterable, reducer, init)[Symbol.asyncIterator]();
}
};

0 comments on commit 8ffd883

Please sign in to comment.