-
Notifications
You must be signed in to change notification settings - Fork 69
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
feat: add `dropWhile` function
- Loading branch information
Showing
4 changed files
with
341 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,153 @@ | ||
import IterableInfer from "../types/IterableInfer"; | ||
import ReturnIterableIteratorType from "../types/ReturnIterableIteratorType"; | ||
import { AsyncFunctionException } from "../_internal/error"; | ||
import { isAsyncIterable, isIterable } from "../_internal/utils"; | ||
import concurrent, { isConcurrent } from "./concurrent"; | ||
|
||
function* sync<A, B>(f: (a: A) => B, iterable: Iterable<A>) { | ||
const iterator = iterable[Symbol.iterator](); | ||
const iterableIterator = { | ||
[Symbol.iterator]() { | ||
return iterator; | ||
}, | ||
}; | ||
|
||
for (const a of iterableIterator) { | ||
const res = f(a); | ||
if (res instanceof Promise) { | ||
throw new AsyncFunctionException(); | ||
} | ||
|
||
if (res) { | ||
continue; | ||
} | ||
yield a; | ||
yield* iterableIterator; | ||
} | ||
} | ||
|
||
async function* asyncSequential<A, B>( | ||
f: (a: A) => B, | ||
iterable: AsyncIterable<A>, | ||
): AsyncIterableIterator<A> { | ||
const iterator = iterable[Symbol.asyncIterator](); | ||
const iterableIterator = { | ||
[Symbol.asyncIterator]() { | ||
return iterator; | ||
}, | ||
}; | ||
|
||
for await (const a of iterableIterator) { | ||
if (await f(a)) { | ||
continue; | ||
} | ||
|
||
yield a; | ||
yield* iterableIterator; | ||
} | ||
} | ||
|
||
function async<A, B>( | ||
f: (a: A) => B, | ||
iterable: AsyncIterable<A>, | ||
): AsyncIterableIterator<A> { | ||
let iterator: AsyncIterator<A>; | ||
return { | ||
[Symbol.asyncIterator]() { | ||
return this; | ||
}, | ||
|
||
async next(_concurrent: any) { | ||
if (iterator === undefined) { | ||
iterator = isConcurrent(_concurrent) | ||
? asyncSequential(f, concurrent(_concurrent.length, iterable)) | ||
: asyncSequential(f, iterable); | ||
} | ||
|
||
return iterator.next(_concurrent); | ||
}, | ||
}; | ||
} | ||
|
||
/** | ||
* Returns Iterable/AsyncIterable excluding elements dropped from the beginning. Elements are dropped until the value applied to `f` returns falsey. | ||
* | ||
* @example | ||
* ```ts | ||
* const iter = dropWhile((a) => a < 3, [1, 2, 3, 4, 5]); | ||
* iter.next(); // {done:false, value: 3} | ||
* iter.next(); // {done:false, value: 4} | ||
* iter.next(); // {done:false, value: 5} | ||
* | ||
* // with pipe | ||
* pipe( | ||
* [1, 2, 3, 4, 5], | ||
* dropWhile((a) => a < 3), | ||
* toArray, | ||
* ); // [3, 4, 5] | ||
* | ||
* await pipe( | ||
* Promise.resolve([1, 2, 3, 4, 5]), | ||
* dropWhile((a) => a < 3), | ||
* toArray, | ||
* ); // [3, 4, 5] | ||
* | ||
* // if you want to use asynchronous callback | ||
* await pipe( | ||
* Promise.resolve([1, 2, 3, 4, 5]), | ||
* toAsync, | ||
* dropWhile(async (a) => a < 3), | ||
* toArray, | ||
* ); // [3, 4, 5] | ||
* | ||
* // with toAsync | ||
* await pipe( | ||
* [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3), Promise.resolve(4), Promise.resolve(5)], | ||
* toAsync, | ||
* dropWhile((a) => a < 3), | ||
* toArray, | ||
* ); // [3, 4, 5] | ||
* | ||
* see {@link https://fxts.dev/docs/pipe | pipe}, {@link https://fxts.dev/docs/toAsync | toAsync}, | ||
* {@link https://fxts.dev/docs/toArray | toArray} | ||
* ``` | ||
*/ | ||
function dropWhile<A, B = unknown>( | ||
f: (a: A) => B, | ||
iterable: Iterable<A>, | ||
): IterableIterator<A>; | ||
|
||
function dropWhile<A, B = unknown>( | ||
f: (a: A) => B, | ||
iterable: AsyncIterable<A>, | ||
): AsyncIterableIterator<A>; | ||
|
||
function dropWhile<A extends Iterable<unknown> | AsyncIterable<unknown>, B>( | ||
f: (a: IterableInfer<A>) => B, | ||
): (iterable: A) => ReturnIterableIteratorType<A>; | ||
|
||
function dropWhile<A extends Iterable<unknown> | AsyncIterable<unknown>, B>( | ||
f: (a: IterableInfer<A>) => B, | ||
iterable?: A, | ||
): | ||
| IterableIterator<IterableInfer<A>> | ||
| AsyncIterableIterator<IterableInfer<A>> | ||
| ((iterable: A) => ReturnIterableIteratorType<A>) { | ||
if (iterable === undefined) { | ||
return (iterable: A) => { | ||
return dropWhile(f, iterable as any) as ReturnIterableIteratorType<A>; | ||
}; | ||
} | ||
|
||
if (isIterable(iterable)) { | ||
return sync(f, iterable as Iterable<IterableInfer<A>>); | ||
} | ||
|
||
if (isAsyncIterable(iterable)) { | ||
return async(f, iterable as AsyncIterable<IterableInfer<A>>); | ||
} | ||
|
||
throw new TypeError("iterable must be type of Iterable or AsyncIterable"); | ||
} | ||
|
||
export default dropWhile; |
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
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,162 @@ | ||
import { | ||
concurrent, | ||
delay, | ||
dropWhile, | ||
filter, | ||
map, | ||
pipe, | ||
toArray, | ||
toAsync, | ||
} from "../../src/index"; | ||
import { Concurrent } from "../../src/Lazy/concurrent"; | ||
import { AsyncFunctionException } from "../../src/_internal/error"; | ||
import { callFuncAfterTime, generatorMock } from "../utils"; | ||
|
||
describe("dropWhile", function () { | ||
describe("sync", function () { | ||
it("should be dropped elements until the value applied to callback returns falsey", function () { | ||
const acc = []; | ||
for (const a of dropWhile((a) => a < 3, [1, 2, 3, 1, 5])) { | ||
acc.push(a); | ||
} | ||
expect(acc).toEqual([3, 1, 5]); | ||
}); | ||
|
||
it("should be able to be used as a curried function in the pipeline", function () { | ||
const res = pipe( | ||
[1, 2, 3, 4, 5, 6, 7, 8], | ||
map((a) => a + 10), | ||
filter((a) => a % 2 === 0), | ||
dropWhile((a) => a < 16), | ||
toArray, | ||
); | ||
|
||
expect(res).toEqual([16, 18]); | ||
}); | ||
|
||
it("should throw an error when the callback is asynchronous", function () { | ||
const res = () => [...dropWhile(async (a) => a > 5, [1, 2, 3, 4, 5])]; | ||
expect(res).toThrowError(new AsyncFunctionException()); | ||
}); | ||
}); | ||
|
||
describe("async", function () { | ||
it("should be dropped elements until the value applied to callback returns falsey", async function () { | ||
const acc = []; | ||
for await (const a of dropWhile((a) => a < 3, toAsync([1, 2, 3, 1, 5]))) { | ||
acc.push(a); | ||
} | ||
expect(acc).toEqual([3, 1, 5]); | ||
}); | ||
|
||
it("should be able to be used as a curried function in the pipeline", async function () { | ||
const res = await pipe( | ||
toAsync([1, 2, 3, 4, 5, 6, 7, 8]), | ||
map((a) => a + 10), | ||
filter((a) => a % 2 === 0), | ||
dropWhile((a) => a < 16), | ||
toArray, | ||
); | ||
|
||
expect(res).toEqual([16, 18]); | ||
}); | ||
|
||
it("should be able to handle an error when asynchronous", async function () { | ||
await expect( | ||
pipe( | ||
toAsync([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), | ||
dropWhile((a) => { | ||
if (a > 5) { | ||
throw new Error("err"); | ||
} | ||
return true; | ||
}), | ||
toArray, | ||
), | ||
).rejects.toThrow("err"); | ||
}); | ||
|
||
it("should be dropped elements concurrently", async function () { | ||
const fn = jest.fn(); | ||
callFuncAfterTime(fn, 400); | ||
const res = await pipe( | ||
toAsync([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), | ||
map((a) => delay(100, a)), | ||
filter((a) => a % 2 === 0), | ||
dropWhile((a) => a < 6), | ||
concurrent(3), | ||
toArray, | ||
); | ||
expect(fn).toBeCalled(); | ||
expect(res).toEqual([6, 8, 10]); | ||
}, 450); | ||
|
||
it("should be controlled the order when concurrency", async function () { | ||
const fn = jest.fn(); | ||
callFuncAfterTime(fn, 1000); | ||
|
||
const res = await pipe( | ||
toAsync( | ||
(function* () { | ||
yield delay(500, 1); | ||
yield delay(400, 2); | ||
yield delay(300, 3); | ||
yield delay(200, 4); | ||
yield delay(100, 5); | ||
yield delay(500, 6); | ||
yield delay(400, 7); | ||
yield delay(300, 8); | ||
yield delay(200, 1); | ||
yield delay(100, 10); | ||
})(), | ||
), | ||
dropWhile((a) => a < 7), | ||
concurrent(5), | ||
toArray, | ||
); | ||
expect(fn).toBeCalled(); | ||
expect(res).toEqual([7, 8, 1, 10]); | ||
}, 1050); | ||
|
||
it("should be able to handle an error when working concurrent", async function () { | ||
await expect( | ||
pipe( | ||
toAsync([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), | ||
dropWhile((a) => { | ||
if (a > 5) { | ||
throw new Error("err"); | ||
} | ||
return true; | ||
}), | ||
concurrent(3), | ||
toArray, | ||
), | ||
).rejects.toThrow("err"); | ||
}); | ||
|
||
it("should be able to handle an error when working concurrent - Promise.reject", async function () { | ||
await expect( | ||
pipe( | ||
toAsync([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), | ||
dropWhile((a) => { | ||
if (a > 5) { | ||
return Promise.reject(new Error("err")); | ||
} | ||
return true; | ||
}), | ||
concurrent(3), | ||
toArray, | ||
), | ||
).rejects.toThrow("err"); | ||
}); | ||
|
||
it("should be passed concurrent object when job works concurrently", async function () { | ||
const mock = generatorMock(); | ||
const iter = dropWhile((a) => a, mock); | ||
const concurrent = Concurrent.of(2) as any; | ||
|
||
await iter.next(concurrent); | ||
expect((mock as any).getConcurrent()).toEqual(concurrent); | ||
}); | ||
}); | ||
}); |
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,24 @@ | ||
import * as Test from "../../src/types/Test"; | ||
import { toAsync, dropWhile, pipe } from "../../src"; | ||
|
||
const { checks, check } = Test; | ||
|
||
const res1 = dropWhile((a) => a, []); | ||
const res2 = dropWhile((a) => a, [1, 2, 3, 4]); | ||
const res3 = dropWhile((a) => a, toAsync([1, 2, 3, 4])); | ||
const res4 = pipe( | ||
[1, 2, 3, 4], | ||
dropWhile((a) => a), | ||
); | ||
const res5 = pipe( | ||
toAsync([1, 2, 3, 4]), | ||
dropWhile((a) => a), | ||
); | ||
|
||
checks([ | ||
check<typeof res1, IterableIterator<never>, Test.Pass>(), | ||
check<typeof res2, IterableIterator<number>, Test.Pass>(), | ||
check<typeof res3, AsyncIterableIterator<number>, Test.Pass>(), | ||
check<typeof res4, IterableIterator<number>, Test.Pass>(), | ||
check<typeof res5, AsyncIterableIterator<number>, Test.Pass>(), | ||
]); |