Skip to content

Commit da56401

Browse files
feat(transducers-async): add raf(), sidechain()
- add tests
1 parent c257fa5 commit da56401

File tree

5 files changed

+129
-2
lines changed

5 files changed

+129
-2
lines changed

packages/transducers-async/package.json

+6
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@
137137
"./push": {
138138
"default": "./push.js"
139139
},
140+
"./raf": {
141+
"default": "./raf.js"
142+
},
140143
"./range": {
141144
"default": "./range.js"
142145
},
@@ -149,6 +152,9 @@
149152
"./run": {
150153
"default": "./run.js"
151154
},
155+
"./sidechain": {
156+
"default": "./sidechain.js"
157+
},
152158
"./step": {
153159
"default": "./step.js"
154160
},

packages/transducers-async/src/index.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,20 @@ export * from "./iterator.js";
1111
export * from "./map.js";
1212
export * from "./mapcat.js";
1313
export * from "./merge.js";
14-
export * from "./multiplex.js";
1514
export * from "./multiplex-obj.js";
15+
export * from "./multiplex.js";
1616
export * from "./partition.js";
1717
export * from "./push.js";
18+
export * from "./raf.js";
1819
export * from "./range.js";
1920
export * from "./reduce.js";
2021
export * from "./repeatedly.js";
2122
export * from "./run.js";
23+
export * from "./sidechain.js";
2224
export * from "./step.js";
2325
export * from "./sync.js";
2426
export * from "./take.js";
25-
export * from "./throttle.js";
2627
export * from "./throttle-time.js";
28+
export * from "./throttle.js";
2729
export * from "./transduce.js";
2830
export * from "./zip.js";

packages/transducers-async/src/raf.ts

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { Fn } from "@thi.ng/api";
2+
3+
export interface RAFOpts {
4+
/**
5+
* If true (default: false), passes the timestamps received
6+
* via `requestAnimationFrame()` as iterator values. If false, a simple
7+
* counter [0..∞) will be emitted.
8+
*
9+
* @defaultValue false
10+
*/
11+
timestamp: boolean;
12+
/**
13+
* Only used if {@link RAFOpts.timestamp} is enabled. If given as
14+
* number, the value will be subtracted from all emitted timestamps. If this
15+
* option is set to true, the timestamps will be automatically zero-adjusted
16+
* such that the first emitted value will be zero. If undefined (default),
17+
* the browser supplied timestamps will be used as is.
18+
*/
19+
t0: number | boolean;
20+
}
21+
22+
export async function* raf(opts?: Partial<RAFOpts>): AsyncGenerator<number> {
23+
let frame = 0;
24+
let t0 = opts?.t0 || 0;
25+
while (true) {
26+
let resolve: Fn<number, void>;
27+
const promise = new Promise<number>(($resolve) => (resolve = $resolve));
28+
requestAnimationFrame(resolve!);
29+
let t = await promise;
30+
if (opts?.timestamp) {
31+
if (t0 === true) t0 = t;
32+
if (t0) t -= t0;
33+
} else {
34+
t = frame++;
35+
}
36+
const cancel = yield t;
37+
if (cancel === true) break;
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
export interface SidechainOpts {
2+
/**
3+
* If true (default), only emits the last received value when the sidechain
4+
* triggers. Otherwise buffers and emits *all* received values since the
5+
* last time the sidechain triggered.
6+
*
7+
* @defaultValue true
8+
*/
9+
lastOnly: boolean;
10+
}
11+
12+
export function sidechain<T>(
13+
src: AsyncIterable<T>,
14+
side: AsyncIterable<boolean>,
15+
opts: Partial<SidechainOpts> & { lastOnly: false }
16+
): AsyncIterableIterator<T[]>;
17+
export function sidechain<T>(
18+
src: AsyncIterable<T>,
19+
side: AsyncIterable<boolean>,
20+
opts?: Partial<SidechainOpts>
21+
): AsyncIterableIterator<T>;
22+
export async function* sidechain<T>(
23+
src: AsyncIterable<T>,
24+
side: AsyncIterable<boolean>,
25+
opts?: Partial<SidechainOpts>
26+
) {
27+
const { lastOnly = true } = opts || {};
28+
const $iter = src[Symbol.asyncIterator]();
29+
const $side = side[Symbol.asyncIterator]();
30+
const promises: Promise<[IteratorResult<any>, boolean?]>[] = [
31+
$iter.next().then((res) => [res]),
32+
$side.next().then((res) => [res, true]),
33+
];
34+
let buf: T[] = [];
35+
while (true) {
36+
const [res, side] = await Promise.any(promises);
37+
if (res.done) return;
38+
if (side) {
39+
promises[1] = $side.next().then((res) => [res, true]);
40+
if (!buf.length) continue;
41+
if (lastOnly) {
42+
yield buf[0];
43+
buf.length = 0;
44+
} else {
45+
yield buf;
46+
buf = [];
47+
}
48+
} else {
49+
promises[0] = $iter.next().then((res) => [res]);
50+
if (lastOnly) buf[0] = res.value;
51+
else buf.push(res.value);
52+
}
53+
}
54+
}

packages/transducers-async/test/main.test.ts

+26
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
range,
1717
repeatedly,
1818
run,
19+
sidechain,
1920
step,
2021
sync,
2122
take,
@@ -197,6 +198,31 @@ test("run", async (done) => {
197198
done();
198199
});
199200

201+
test.only(
202+
"sidechain",
203+
async (done) => {
204+
expect(
205+
await push(
206+
sidechain(
207+
repeatedly((i) => i, 100, 10),
208+
repeatedly(() => true, 3, 25)
209+
)
210+
)
211+
).toEqual([0, 2, 4]);
212+
expect(
213+
await push(
214+
sidechain(
215+
repeatedly((i) => i, 100, 10),
216+
repeatedly(() => true, 3, 25),
217+
{ lastOnly: false }
218+
)
219+
)
220+
).toEqual([[0], [1, 2], [3, 4]]);
221+
done();
222+
},
223+
{ retry: 5 }
224+
);
225+
200226
test("step", async (done) => {
201227
expect(await step(mapcat(async (x) => [x, x]))(1)).toEqual([1, 1]);
202228
expect(await step(mapcat(async (x) => [x]))(1)).toEqual(1);

0 commit comments

Comments
 (0)