-
Notifications
You must be signed in to change notification settings - Fork 72
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
200 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,102 @@ | ||
import { isAsyncIterable, isIterable } from "../_internal/utils"; | ||
import isNil from "../isNil"; | ||
import type IterableInfer from "../types/IterableInfer"; | ||
|
||
type ReturnForkType<A extends Iterable<unknown> | AsyncIterable<unknown>> = | ||
A extends AsyncIterable<any> | ||
? AsyncIterableIterator<IterableInfer<A>> | ||
: IterableIterator<IterableInfer<A>>; | ||
|
||
type Node<T> = { value: T; done?: boolean }; | ||
|
||
class ForkItem<T> { | ||
node: Node<T>; | ||
nextNode: ForkItem<T> | null; | ||
|
||
constructor(node: Node<T>) { | ||
this.node = node; | ||
this.nextNode = null; | ||
} | ||
} | ||
|
||
class ForkQueue<T> { | ||
head: ForkItem<T>; | ||
|
||
current: ForkItem<T>; | ||
|
||
constructor() { | ||
this.head = new ForkItem(null as any); | ||
this.current = this.head; | ||
} | ||
|
||
toString() { | ||
const arr = []; | ||
let cur: ForkItem<T> | null = this.head.nextNode; | ||
while (cur) { | ||
arr.push(cur.node.value); | ||
cur = cur.nextNode; | ||
} | ||
|
||
return arr.join(", "); | ||
} | ||
} | ||
|
||
const forkMap = new WeakMap<any, ForkQueue<any>>(); | ||
|
||
function sync<T>(iterable: Iterable<T>) { | ||
const iterator = iterable[Symbol.iterator](); | ||
let queue = forkMap.get(iterator) as ForkQueue<any>; | ||
if (!queue) { | ||
queue = new ForkQueue(); | ||
forkMap.set(iterator, queue); | ||
} | ||
|
||
let cur = queue.current; | ||
let done = false; | ||
|
||
return { | ||
[Symbol.iterator]() { | ||
return iterator; | ||
}, | ||
|
||
next() { | ||
if (done) { | ||
return { | ||
done, | ||
value: undefined, | ||
}; | ||
} | ||
|
||
const item = cur.nextNode; | ||
if (isNil(item)) { | ||
const node = iterator.next(); | ||
cur.nextNode = new ForkItem(node); | ||
cur = cur.nextNode; | ||
|
||
queue.current = cur; | ||
done = node.done ?? true; | ||
|
||
return cur.node; | ||
} | ||
|
||
cur = item; | ||
return cur.node; | ||
}, | ||
}; | ||
} | ||
|
||
function fork<A extends Iterable<unknown> | AsyncIterable<unknown>>( | ||
iterable: A, | ||
): ReturnForkType<A> { | ||
if (isIterable(iterable)) { | ||
return sync(iterable) as ReturnForkType<A>; | ||
} | ||
|
||
if (isAsyncIterable(iterable)) { | ||
throw new TypeError("'fork' asyncIterable isn't supported not yet"); | ||
} | ||
|
||
throw new TypeError("'iterable' must be type of Iterable or AsyncIterable"); | ||
} | ||
|
||
export default fork; |
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,79 @@ | ||
import { fork, map, pipe } from "../../src/index"; | ||
|
||
describe("fork", function () { | ||
describe("sync", function () { | ||
it("should be forked iterable(number)", function () { | ||
const arr = [1, 2, 3]; | ||
|
||
const iter1 = fork(arr); | ||
const iter2 = fork(arr); | ||
|
||
expect(iter1.next()).toEqual({ value: 1, done: false }); | ||
expect(iter1.next()).toEqual({ value: 2, done: false }); | ||
expect(iter1.next()).toEqual({ value: 3, done: false }); | ||
expect(iter1.next()).toEqual({ value: undefined, done: true }); | ||
|
||
expect(iter2.next()).toEqual({ value: 1, done: false }); | ||
expect(iter2.next()).toEqual({ value: 2, done: false }); | ||
expect(iter2.next()).toEqual({ value: 3, done: false }); | ||
expect(iter2.next()).toEqual({ value: undefined, done: true }); | ||
}); | ||
|
||
it("should be forked iterable(string)", function () { | ||
const arr = "abc"; | ||
|
||
const iter1 = fork(arr); | ||
const iter2 = fork(arr); | ||
|
||
expect(iter1.next()).toEqual({ value: "a", done: false }); | ||
expect(iter1.next()).toEqual({ value: "b", done: false }); | ||
expect(iter1.next()).toEqual({ value: "c", done: false }); | ||
expect(iter1.next()).toEqual({ value: undefined, done: true }); | ||
|
||
expect(iter2.next()).toEqual({ value: "a", done: false }); | ||
expect(iter2.next()).toEqual({ value: "b", done: false }); | ||
expect(iter2.next()).toEqual({ value: "c", done: false }); | ||
expect(iter2.next()).toEqual({ value: undefined, done: true }); | ||
}); | ||
|
||
it("should be able to be used as a forked function in the pipeline", function () { | ||
const arr = pipe( | ||
[1, 2, 3], | ||
map((a) => a + 10), | ||
); | ||
|
||
const iter1 = fork(arr); | ||
const iter2 = fork(arr); | ||
expect(iter1.next()).toEqual({ value: 11, done: false }); | ||
expect(iter2.next()).toEqual({ value: 11, done: false }); | ||
|
||
expect(iter1.next()).toEqual({ value: 12, done: false }); | ||
expect(iter1.next()).toEqual({ value: 13, done: false }); | ||
expect(iter1.next()).toEqual({ value: undefined, done: true }); | ||
|
||
expect(iter2.next()).toEqual({ value: 12, done: false }); | ||
expect(iter2.next()).toEqual({ value: 13, done: false }); | ||
expect(iter2.next()).toEqual({ value: undefined, done: true }); | ||
}); | ||
|
||
it("should be forked in the middle of iterable progress", function () { | ||
const arr = pipe( | ||
[1, 2, 3], | ||
map((a) => a + 10), | ||
); | ||
|
||
const iter1 = fork(arr); | ||
const iter2 = fork(iter1); | ||
|
||
expect(iter1.next()).toEqual({ value: 11, done: false }); | ||
expect(iter2.next()).toEqual({ value: 11, done: false }); | ||
|
||
const iter3 = fork(iter1); | ||
expect(iter1.next()).toEqual({ value: 12, done: false }); | ||
expect(iter1.next()).toEqual({ value: 13, done: false }); | ||
expect(iter1.next()).toEqual({ value: undefined, done: true }); | ||
expect(iter2.next()).toEqual({ value: 12, done: false }); | ||
expect(iter3.next()).toEqual({ value: 12, done: false }); | ||
}); | ||
}); | ||
}); |
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,17 @@ | ||
import { fork, pipe } from "../../src"; | ||
import * as Test from "../../src/types/Test"; | ||
|
||
const { checks, check } = Test; | ||
|
||
const res1 = fork([1, 2, 3]); | ||
const res2 = fork("abc"); | ||
|
||
const res3 = pipe([1, 2, 3], fork); | ||
const res4 = pipe("abc", fork); | ||
|
||
checks([ | ||
check<typeof res1, IterableIterator<number>, Test.Pass>(), | ||
check<typeof res2, IterableIterator<string>, Test.Pass>(), | ||
check<typeof res3, IterableIterator<number>, Test.Pass>(), | ||
check<typeof res4, IterableIterator<string>, Test.Pass>(), | ||
]); |