+5
.changeset/beige-worms-help.md
+5
.changeset/beige-worms-help.md
+160
src/__tests__/sinks.test.ts
+160
src/__tests__/sinks.test.ts
···
229
229
});
230
230
});
231
231
232
+
describe('toAsyncIterable', () => {
233
+
it('creates an async iterable mirroring the Wonka source', async () => {
234
+
let pulls = 0;
235
+
let sink: Sink<any> | null = null;
236
+
237
+
const source: Source<any> = _sink => {
238
+
sink = _sink;
239
+
sink(
240
+
start(signal => {
241
+
if (signal === TalkbackKind.Pull) pulls++;
242
+
})
243
+
);
244
+
};
245
+
246
+
const asyncIterator = sinks.toAsyncIterable(source)[Symbol.asyncIterator]();
247
+
248
+
expect(pulls).toBe(1);
249
+
sink!(push(0));
250
+
expect(await asyncIterator.next()).toEqual({ value: 0, done: false });
251
+
expect(pulls).toBe(2);
252
+
253
+
sink!(push(1));
254
+
expect(await asyncIterator.next()).toEqual({ value: 1, done: false });
255
+
expect(pulls).toBe(3);
256
+
257
+
sink!(SignalKind.End);
258
+
expect(await asyncIterator.next()).toEqual({ done: true });
259
+
expect(pulls).toBe(3);
260
+
});
261
+
262
+
it('buffers actively pushed values', async () => {
263
+
let pulls = 0;
264
+
let sink: Sink<any> | null = null;
265
+
266
+
const source: Source<any> = _sink => {
267
+
sink = _sink;
268
+
sink(
269
+
start(signal => {
270
+
if (signal === TalkbackKind.Pull) pulls++;
271
+
})
272
+
);
273
+
};
274
+
275
+
const asyncIterator = sinks.toAsyncIterable(source)[Symbol.asyncIterator]();
276
+
277
+
sink!(push(0));
278
+
sink!(push(1));
279
+
sink!(SignalKind.End);
280
+
281
+
expect(pulls).toBe(1);
282
+
expect(await asyncIterator.next()).toEqual({ value: 0, done: false });
283
+
expect(await asyncIterator.next()).toEqual({ value: 1, done: false });
284
+
expect(await asyncIterator.next()).toEqual({ done: true });
285
+
});
286
+
287
+
it('asynchronously waits for pulled values', async () => {
288
+
let pulls = 0;
289
+
let sink: Sink<any> | null = null;
290
+
291
+
const source: Source<any> = _sink => {
292
+
sink = _sink;
293
+
sink(
294
+
start(signal => {
295
+
if (signal === TalkbackKind.Pull) pulls++;
296
+
})
297
+
);
298
+
};
299
+
300
+
const asyncIterator = sinks.toAsyncIterable(source)[Symbol.asyncIterator]();
301
+
expect(pulls).toBe(1);
302
+
303
+
let resolved = false;
304
+
305
+
const promise = asyncIterator.next().then(value => {
306
+
resolved = true;
307
+
return value;
308
+
});
309
+
310
+
await Promise.resolve();
311
+
expect(resolved).toBe(false);
312
+
313
+
sink!(push(0));
314
+
sink!(SignalKind.End);
315
+
expect(await promise).toEqual({ value: 0, done: false });
316
+
expect(await asyncIterator.next()).toEqual({ done: true });
317
+
});
318
+
319
+
it('supports cancellation via return', async () => {
320
+
let ended = false;
321
+
let sink: Sink<any> | null = null;
322
+
323
+
const source: Source<any> = _sink => {
324
+
sink = _sink;
325
+
sink(
326
+
start(signal => {
327
+
if (signal === TalkbackKind.Close) ended = true;
328
+
})
329
+
);
330
+
};
331
+
332
+
const asyncIterator = sinks.toAsyncIterable(source)[Symbol.asyncIterator]();
333
+
334
+
sink!(push(0));
335
+
expect(await asyncIterator.next()).toEqual({ value: 0, done: false });
336
+
expect(await asyncIterator.return!()).toEqual({ done: true });
337
+
338
+
sink!(push(1));
339
+
expect(await asyncIterator.next()).toEqual({ done: true });
340
+
341
+
expect(ended).toBeTruthy();
342
+
});
343
+
344
+
it('supports for-await-of', async () => {
345
+
let pulls = 0;
346
+
347
+
const source: Source<any> = sink => {
348
+
sink(
349
+
start(signal => {
350
+
if (signal === TalkbackKind.Pull) {
351
+
sink(pulls < 3 ? push(pulls++) : SignalKind.End);
352
+
}
353
+
})
354
+
);
355
+
};
356
+
357
+
const iterable = sinks.toAsyncIterable(source);
358
+
const values: any[] = [];
359
+
for await (const value of iterable) {
360
+
values.push(value);
361
+
}
362
+
363
+
expect(values).toEqual([0, 1, 2]);
364
+
});
365
+
366
+
it('supports for-await-of with early break', async () => {
367
+
let pulls = 0;
368
+
let closed = false;
369
+
370
+
const source: Source<any> = sink => {
371
+
sink(
372
+
start(signal => {
373
+
if (signal === TalkbackKind.Pull) {
374
+
sink(pulls < 3 ? push(pulls++) : SignalKind.End);
375
+
} else {
376
+
closed = true;
377
+
}
378
+
})
379
+
);
380
+
};
381
+
382
+
const iterable = sinks.toAsyncIterable(source);
383
+
for await (const value of iterable) {
384
+
expect(value).toBe(0);
385
+
break;
386
+
}
387
+
388
+
expect(closed).toBe(true);
389
+
});
390
+
});
391
+
232
392
describe('toObservable', () => {
233
393
it('creates an Observable mirroring the Wonka source', () => {
234
394
const next = vi.fn();
+46
src/sinks.ts
+46
src/sinks.ts
···
38
38
})(source);
39
39
}
40
40
41
+
const doneResult = { done: true } as IteratorReturnResult<void>;
42
+
43
+
export const toAsyncIterable = <T>(source: Source<T>): AsyncIterable<T> => ({
44
+
[Symbol.asyncIterator](): AsyncIterator<T> {
45
+
const buffer: T[] = [];
46
+
47
+
let ended = false;
48
+
let talkback = talkbackPlaceholder;
49
+
let next: ((value: IteratorResult<T>) => void) | void;
50
+
51
+
source(signal => {
52
+
if (ended) {
53
+
/*noop*/
54
+
} else if (signal === SignalKind.End) {
55
+
if (next) next = next(doneResult);
56
+
ended = true;
57
+
} else if (signal.tag === SignalKind.Start) {
58
+
(talkback = signal[0])(TalkbackKind.Pull);
59
+
} else if (next) {
60
+
next = next({ value: signal[0], done: false });
61
+
} else {
62
+
buffer.push(signal[0]);
63
+
}
64
+
});
65
+
66
+
return {
67
+
async next(): Promise<IteratorResult<T>> {
68
+
if (ended && !buffer.length) {
69
+
return doneResult;
70
+
} else if (!ended && buffer.length <= 1) {
71
+
talkback(TalkbackKind.Pull);
72
+
}
73
+
74
+
return buffer.length
75
+
? { value: buffer.shift()!, done: false }
76
+
: new Promise(resolve => (next = resolve));
77
+
},
78
+
async return(): Promise<IteratorReturnResult<void>> {
79
+
if (!ended) next = talkback(TalkbackKind.Close);
80
+
ended = true;
81
+
return doneResult;
82
+
},
83
+
};
84
+
},
85
+
});
86
+
41
87
export function toArray<T>(source: Source<T>): T[] {
42
88
const values: T[] = [];
43
89
let talkback = talkbackPlaceholder;