@@ -4,6 +4,26 @@ export type SyncSources<T extends Record<NumOrString, any>> = {
4
4
[ id in keyof T ] : AsyncIterable < T [ id ] > ;
5
5
} ;
6
6
7
+ export interface SyncOpts {
8
+ /**
9
+ * If true, {@link sync} waits for new values from *all* remaining inputs
10
+ * before a new tuple is produced. If false, that synchronization only
11
+ * happens for the very first tuple and afterwards any changed input will
12
+ * trigger a tuple update.
13
+ *
14
+ * @defaultValue false
15
+ */
16
+ reset : boolean ;
17
+ /**
18
+ * Only used if {@link SyncOpts.reset} is disabled. If true (default: false)
19
+ * *no* input synchronization (waiting for values) is applied and
20
+ * {@link sync} will emit potentially partially populated tuple objects for
21
+ * each received input value. However, as with the default behavior, tuples
22
+ * will retain the most recent consumed value from other inputs.
23
+ */
24
+ mergeOnly : boolean ;
25
+ }
26
+
7
27
/**
8
28
* Async iterator version of [thi.ng/rstream's sync()
9
29
* construct](https://docs.thi.ng/umbrella/rstream/functions/sync.html).
@@ -12,10 +32,20 @@ export type SyncSources<T extends Record<NumOrString, any>> = {
12
32
* Also see {@link merge} for an alternative way of merging.
13
33
*
14
34
* @param src
35
+ * @param opts
15
36
*/
37
+ export function sync < T extends Record < NumOrString , any > > (
38
+ src : SyncSources < T > ,
39
+ opts ?: Partial < SyncOpts > & { mergeOnly : true }
40
+ ) : AsyncIterableIterator < Partial < T > > ;
41
+ export function sync < T extends Record < NumOrString , any > > (
42
+ src : SyncSources < T > ,
43
+ opts ?: Partial < SyncOpts >
44
+ ) : AsyncIterableIterator < T > ;
16
45
export async function * sync < T extends Record < NumOrString , any > > (
17
- src : SyncSources < T >
18
- ) : AsyncIterableIterator < T > {
46
+ src : SyncSources < T > ,
47
+ opts ?: Partial < SyncOpts >
48
+ ) {
19
49
let iters = < { key : keyof T ; id : number ; iter : AsyncIterator < any > } [ ] > (
20
50
Object . entries ( src ) . map ( ( [ key , v ] , id ) => ( {
21
51
key,
@@ -26,38 +56,57 @@ export async function* sync<T extends Record<NumOrString, any>>(
26
56
let n = iters . length ;
27
57
const $remove = ( id : number ) => {
28
58
iters . splice ( id , 1 ) ;
29
- n -- ;
30
- if ( ! n ) return true ;
59
+ if ( ! -- n ) return true ;
31
60
for ( let i = id ; i < n ; i ++ ) iters [ i ] . id -- ;
32
61
} ;
33
- // wait for all sources
34
- const initial = await Promise . all ( iters . map ( ( { iter } ) => iter . next ( ) ) ) ;
35
- // keep active iterators only, update successive IDs
36
- for ( let i = 0 ; i < n ; ) {
37
- if ( initial [ i ] . done ) {
38
- initial . splice ( i , 1 ) ;
39
- if ( $remove ( i ) ) return ;
40
- } else i ++ ;
41
- }
42
- // build initial tuple
43
- const tuple = initial . reduce (
44
- ( acc , x , i ) => ( ( acc [ iters [ i ] . key ] = x . value ) , acc ) ,
45
- < T > { }
46
- ) ;
47
- yield { ...tuple } ;
48
- // array of in-flight promises
49
- const promises = iters . map ( ( iter ) =>
50
- iter . iter . next ( ) . then ( ( res ) => ( { iter, res } ) )
51
- ) ;
52
- while ( true ) {
53
- const { iter, res } = await Promise . any ( promises ) ;
54
- if ( res . done ) {
55
- promises . splice ( iter . id , 1 ) ;
56
- if ( $remove ( iter . id ) ) return ;
62
+ const $initial = async ( ) => {
63
+ // wait for all sources
64
+ const res = await Promise . all ( iters . map ( ( { iter } ) => iter . next ( ) ) ) ;
65
+ // keep active iterators only, update successive IDs
66
+ for ( let i = 0 ; i < n ; ) {
67
+ if ( res [ i ] . done ) {
68
+ res . splice ( i , 1 ) ;
69
+ if ( $remove ( i ) ) return ;
70
+ } else i ++ ;
71
+ }
72
+ // build tuple
73
+ return res . reduce (
74
+ ( acc , x , i ) => ( ( acc [ iters [ i ] . key ] = x . value ) , acc ) ,
75
+ < T > { }
76
+ ) ;
77
+ } ;
78
+ if ( opts ?. reset ) {
79
+ let tuple : T | undefined ;
80
+ let curr : T | undefined ;
81
+ while ( ( curr = await $initial ( ) ) ) {
82
+ tuple = { ...tuple , ...curr } ;
83
+ yield tuple ;
84
+ }
85
+ } else {
86
+ let tuple : T | undefined ;
87
+ if ( opts ?. mergeOnly ) {
88
+ tuple = < T > { } ;
57
89
} else {
58
- tuple [ iter . key ] = res . value ;
90
+ tuple = await $initial ( ) ;
91
+ if ( ! tuple ) return ;
59
92
yield { ...tuple } ;
60
- promises [ iter . id ] = iter . iter . next ( ) . then ( ( res ) => ( { res, iter } ) ) ;
93
+ }
94
+ // array of in-flight promises
95
+ const promises = iters . map ( ( iter ) =>
96
+ iter . iter . next ( ) . then ( ( res ) => ( { iter, res } ) )
97
+ ) ;
98
+ while ( true ) {
99
+ const { iter, res } = await Promise . any ( promises ) ;
100
+ if ( res . done ) {
101
+ promises . splice ( iter . id , 1 ) ;
102
+ if ( $remove ( iter . id ) ) return ;
103
+ } else {
104
+ tuple [ iter . key ] = res . value ;
105
+ yield { ...tuple } ;
106
+ promises [ iter . id ] = iter . iter
107
+ . next ( )
108
+ . then ( ( res ) => ( { res, iter } ) ) ;
109
+ }
61
110
}
62
111
}
63
112
}
0 commit comments