Skip to content

Commit 7a99397

Browse files
authored
feat(groupBy): Support named arguments, support ObservableInputs for duration selector (#5679)
* feat(groupBy): Support named arguments, support ObservableInputs for duration selector - Adds support for named arguments. - Adds support for returning promises, et al, from the duration selector. NOTES: * The tests for `groupBy` appear to be EXTREMELY old and outdated, and I was unable to updated them easily to use run mode. We may have to rewrite them all at some point to use better techniques. The issue seems to be a rudementary means of testing the inner observables that is incompatible with run mode. * Docs still need updated * Older paths still need to be deprecated * dtslint tests need to be added * chore: rebased. There are still type issues * refactor: change to fn, options pattern * chore: remove duplicate typing
1 parent 190e4d0 commit 7a99397

File tree

3 files changed

+109
-72
lines changed

3 files changed

+109
-72
lines changed

api_guard/dist/types/operators/index.d.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,13 @@ export declare function first<T, D = T>(predicate: (value: T, index: number, sou
120120

121121
export declare const flatMap: typeof mergeMap;
122122

123-
export declare function groupBy<T, K extends T>(keySelector: (value: T) => value is K): OperatorFunction<T, GroupedObservable<true, K> | GroupedObservable<false, Exclude<T, K>>>;
124-
export declare function groupBy<T, K>(keySelector: (value: T) => K): OperatorFunction<T, GroupedObservable<K, T>>;
125-
export declare function groupBy<T, K>(keySelector: (value: T) => K, elementSelector: void, durationSelector: (grouped: GroupedObservable<K, T>) => Observable<any>): OperatorFunction<T, GroupedObservable<K, T>>;
126-
export declare function groupBy<T, K, R>(keySelector: (value: T) => K, elementSelector?: (value: T) => R, durationSelector?: (grouped: GroupedObservable<K, R>) => Observable<any>): OperatorFunction<T, GroupedObservable<K, R>>;
127-
export declare function groupBy<T, K, R>(keySelector: (value: T) => K, elementSelector?: (value: T) => R, durationSelector?: (grouped: GroupedObservable<K, R>) => Observable<any>, subjectSelector?: () => Subject<R>): OperatorFunction<T, GroupedObservable<K, R>>;
123+
export declare function groupBy<T, K>(key: (value: T) => K, options: BasicGroupByOptions<K, T>): OperatorFunction<T, GroupedObservable<K, T>>;
124+
export declare function groupBy<T, K, E>(key: (value: T) => K, options: GroupByOptionsWithElement<K, E, T>): OperatorFunction<T, GroupedObservable<K, E>>;
125+
export declare function groupBy<T, K extends T>(key: (value: T) => value is K): OperatorFunction<T, GroupedObservable<true, K> | GroupedObservable<false, Exclude<T, K>>>;
126+
export declare function groupBy<T, K>(key: (value: T) => K): OperatorFunction<T, GroupedObservable<K, T>>;
127+
export declare function groupBy<T, K>(key: (value: T) => K, element: void, duration: (grouped: GroupedObservable<K, T>) => Observable<any>): OperatorFunction<T, GroupedObservable<K, T>>;
128+
export declare function groupBy<T, K, R>(key: (value: T) => K, element?: (value: T) => R, duration?: (grouped: GroupedObservable<K, R>) => Observable<any>): OperatorFunction<T, GroupedObservable<K, R>>;
129+
export declare function groupBy<T, K, R>(key: (value: T) => K, element?: (value: T) => R, duration?: (grouped: GroupedObservable<K, R>) => Observable<any>, connector?: () => Subject<R>): OperatorFunction<T, GroupedObservable<K, R>>;
128130

129131
export declare function ignoreElements(): OperatorFunction<any, never>;
130132

spec/operators/groupBy-spec.ts

Lines changed: 32 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ describe('groupBy operator', () => {
4242
];
4343

4444
of(1, 2, 3).pipe(
45-
groupBy((x: number) => x % 2)
45+
groupBy((x) => x % 2)
4646
).subscribe((g: any) => {
4747
const expectedGroup = expectedGroups.shift()!;
4848
expect(g.key).to.equal(expectedGroup.key);
@@ -60,7 +60,7 @@ describe('groupBy operator', () => {
6060
];
6161

6262
of(1, 2, 3).pipe(
63-
groupBy((x: number) => x % 2, (x: number) => x + '!')
63+
groupBy((x) => x % 2, (x) => x + '!')
6464
).subscribe((g: any) => {
6565
const expectedGroup = expectedGroups.shift()!;
6666
expect(g.key).to.equal(expectedGroup.key);
@@ -82,21 +82,20 @@ describe('groupBy operator', () => {
8282
const resultingGroups: { key: number, values: number [] }[] = [];
8383

8484
of(1, 2, 3, 4, 5, 6).pipe(
85-
groupBy(
86-
(x: number) => x % 2,
87-
(x: number) => x,
88-
(g: any) => g.pipe(skip(1)))
89-
).subscribe((g: any) => {
90-
let group = { key: g.key, values: [] as number[] };
91-
92-
g.subscribe((x: any) => {
93-
group.values.push(x);
94-
});
85+
groupBy(x => x % 2, {
86+
duration: g => g.pipe(skip(1))
87+
})
88+
).subscribe((g: any) => {
89+
let group = { key: g.key, values: [] as number[] };
9590

96-
resultingGroups.push(group);
91+
g.subscribe((x: any) => {
92+
group.values.push(x);
9793
});
9894

99-
expect(resultingGroups).to.deep.equal(expectedGroups);
95+
resultingGroups.push(group);
96+
});
97+
98+
expect(resultingGroups).to.deep.equal(expectedGroups);
10099
});
101100

102101
it('should group values with a subject selector', (done) => {
@@ -106,7 +105,9 @@ describe('groupBy operator', () => {
106105
];
107106

108107
of(1, 2, 3).pipe(
109-
groupBy((x: number) => x % 2, null as any, null as any, () => new ReplaySubject(1)),
108+
groupBy(x => x % 2, {
109+
connector: () => new ReplaySubject(1),
110+
}),
110111
// Ensure each inner group reaches the destination after the first event
111112
// has been next'd to the group
112113
delay(5)
@@ -802,11 +803,11 @@ describe('groupBy operator', () => {
802803
const expectedValues = { v: v, w: w, x: x, y: y, z: z };
803804

804805
const source = e1
805-
.pipe(groupBy(
806-
(val: string) => val.toLowerCase().trim(),
807-
(val: string) => val,
808-
(group: any) => group.pipe(skip(2))
809-
));
806+
.pipe(
807+
groupBy(val => val.toLowerCase().trim(), {
808+
duration: group => group.pipe(skip(2)),
809+
})
810+
);
810811

811812
expectObservable(source).toBe(expected, expectedValues);
812813
expectSubscriptions(e1.subscriptions).toBe(e1subs);
@@ -836,11 +837,9 @@ describe('groupBy operator', () => {
836837
const expectedValues = { v: v, w: w, x: x };
837838

838839
const source = e1
839-
.pipe(groupBy(
840-
(val: string) => val.toLowerCase().trim(),
841-
(val: string) => val,
842-
(group: any) => group.pipe(skip(2))
843-
));
840+
.pipe(groupBy(val => val.toLowerCase().trim(), {
841+
duration: group => group.pipe(skip(2))
842+
}));
844843

845844
expectObservable(source, unsub).toBe(expected, expectedValues);
846845
});
@@ -879,17 +878,15 @@ describe('groupBy operator', () => {
879878
.unsubscribedFrame;
880879

881880
const source = e1.pipe(
882-
groupBy(
883-
(val: string) => val.toLowerCase().trim(),
884-
(val: string) => val,
885-
(group: any) => group.pipe(skip(2))
886-
),
887-
map((group: any) => {
881+
groupBy(val => val.toLowerCase().trim(), {
882+
duration: group => group.pipe(skip(2))
883+
}),
884+
map((group) => {
888885
const arr: any[] = [];
889886

890887
const subscription = group.pipe(
891888
phonyMarbelize()
892-
).subscribe((value: any) => {
889+
).subscribe((value) => {
893890
arr.push(value);
894891
});
895892

@@ -923,11 +920,9 @@ describe('groupBy operator', () => {
923920
.parseMarblesAsSubscriptions(sub)
924921
.unsubscribedFrame;
925922

926-
obs.pipe(groupBy(
927-
(val: string) => val,
928-
(val: string) => val,
929-
(group: any) => durations[group.key]
930-
)).subscribe();
923+
obs.pipe(groupBy((val) => val, {
924+
duration: (group) => durations[Number(group.key)]
925+
})).subscribe();
931926

932927
rxTestScheduler.schedule(() => {
933928
durations.forEach((d, i) => {

src/internal/operators/groupBy.ts

Lines changed: 70 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,51 @@
11
import { Observable } from '../Observable';
2+
import { innerFrom } from '../observable/from';
23
import { Subject } from '../Subject';
3-
import { Observer, OperatorFunction } from '../types';
4+
import { ObservableInput, Observer, OperatorFunction, SubjectLike } from '../types';
45
import { operate } from '../util/lift';
56
import { OperatorSubscriber } from './OperatorSubscriber';
67

8+
interface BasicGroupByOptions<K, T> {
9+
element?: undefined;
10+
duration?: (grouped: GroupedObservable<K, T>) => ObservableInput<any>;
11+
connector?: () => SubjectLike<T>;
12+
}
13+
14+
interface GroupByOptionsWithElement<K, E, T> {
15+
element: (value: T) => E;
16+
duration?: (grouped: GroupedObservable<K, E>) => ObservableInput<any>;
17+
connector?: () => SubjectLike<E>;
18+
}
19+
20+
export function groupBy<T, K>(key: (value: T) => K, options: BasicGroupByOptions<K, T>): OperatorFunction<T, GroupedObservable<K, T>>;
21+
22+
export function groupBy<T, K, E>(
23+
key: (value: T) => K,
24+
options: GroupByOptionsWithElement<K, E, T>
25+
): OperatorFunction<T, GroupedObservable<K, E>>;
26+
727
export function groupBy<T, K extends T>(
8-
keySelector: (value: T) => value is K
28+
key: (value: T) => value is K
929
): OperatorFunction<T, GroupedObservable<true, K> | GroupedObservable<false, Exclude<T, K>>>;
10-
export function groupBy<T, K>(keySelector: (value: T) => K): OperatorFunction<T, GroupedObservable<K, T>>;
30+
31+
export function groupBy<T, K>(key: (value: T) => K): OperatorFunction<T, GroupedObservable<K, T>>;
32+
33+
/**
34+
* @deprecated use the options parameter instead.
35+
*/
1136
export function groupBy<T, K>(
12-
keySelector: (value: T) => K,
13-
elementSelector: void,
14-
durationSelector: (grouped: GroupedObservable<K, T>) => Observable<any>
37+
key: (value: T) => K,
38+
element: void,
39+
duration: (grouped: GroupedObservable<K, T>) => Observable<any>
1540
): OperatorFunction<T, GroupedObservable<K, T>>;
41+
42+
/**
43+
* @deprecated use the options parameter instead.
44+
*/
1645
export function groupBy<T, K, R>(
17-
keySelector: (value: T) => K,
18-
elementSelector?: (value: T) => R,
19-
durationSelector?: (grouped: GroupedObservable<K, R>) => Observable<any>
20-
): OperatorFunction<T, GroupedObservable<K, R>>;
21-
export function groupBy<T, K, R>(
22-
keySelector: (value: T) => K,
23-
elementSelector?: (value: T) => R,
24-
durationSelector?: (grouped: GroupedObservable<K, R>) => Observable<any>,
25-
subjectSelector?: () => Subject<R>
46+
key: (value: T) => K,
47+
element?: (value: T) => R,
48+
duration?: (grouped: GroupedObservable<K, R>) => Observable<any>
2649
): OperatorFunction<T, GroupedObservable<K, R>>;
2750

2851
/**
@@ -32,7 +55,7 @@ export function groupBy<T, K, R>(
3255
*
3356
* ![](groupBy.png)
3457
*
35-
* When the Observable emits an item, a key is computed for this item with the keySelector function.
58+
* When the Observable emits an item, a key is computed for this item with the key function.
3659
*
3760
* If a {@link GroupedObservable} for this key exists, this {@link GroupedObservable} emits. Otherwise, a new
3861
* {@link GroupedObservable} for this key is created and emits.
@@ -41,7 +64,7 @@ export function groupBy<T, K, R>(
4164
* key is available as the `key` field of a {@link GroupedObservable} instance.
4265
*
4366
* The elements emitted by {@link GroupedObservable}s are by default the items emitted by the Observable, or elements
44-
* returned by the elementSelector function.
67+
* returned by the element function.
4568
*
4669
* ## Examples
4770
*
@@ -101,28 +124,45 @@ export function groupBy<T, K, R>(
101124
* // { id: 3, values: [ 'TSLint' ] }
102125
* ```
103126
*
104-
* @param {function(value: T): K} keySelector A function that extracts the key
127+
* @param key A function that extracts the key
105128
* for each item.
106-
* @param {function(value: T): R} [elementSelector] A function that extracts the
129+
* @param element A function that extracts the
107130
* return element for each item.
108-
* @param {function(grouped: GroupedObservable<K,R>): Observable<any>} [durationSelector]
131+
* @param duration
109132
* A function that returns an Observable to determine how long each group should
110133
* exist.
111-
* @param {function(): Subject<R>} [subjectSelector] Factory function to create an
134+
* @param connector Factory function to create an
112135
* intermediate Subject through which grouped elements are emitted.
113136
* @return A function that returns an Observable that emits GroupedObservables,
114137
* each of which corresponds to a unique key value and each of which emits
115138
* those items from the source Observable that share that key value.
139+
*
140+
* @deprecated Use the options parameter instead.
116141
*/
142+
export function groupBy<T, K, R>(
143+
key: (value: T) => K,
144+
element?: (value: T) => R,
145+
duration?: (grouped: GroupedObservable<K, R>) => Observable<any>,
146+
connector?: () => Subject<R>
147+
): OperatorFunction<T, GroupedObservable<K, R>>;
148+
149+
// Impl
117150
export function groupBy<T, K, R>(
118151
keySelector: (value: T) => K,
119-
elementSelector?: ((value: T) => R) | void,
120-
durationSelector?: (grouped: GroupedObservable<K, R>) => Observable<any>,
121-
subjectSelector?: () => Subject<R>
152+
elementOrOptions?: ((value: any) => any) | void | BasicGroupByOptions<K, T> | GroupByOptionsWithElement<K, R, T>,
153+
duration?: (grouped: GroupedObservable<any, any>) => ObservableInput<any>,
154+
connector?: () => SubjectLike<any>
122155
): OperatorFunction<T, GroupedObservable<K, R>> {
123156
return operate((source, subscriber) => {
157+
let element: ((value: any) => any) | void;
158+
if (!elementOrOptions || typeof elementOrOptions === 'function') {
159+
element = elementOrOptions;
160+
} else {
161+
({ duration, element, connector } = elementOrOptions);
162+
}
163+
124164
// A lookup for the groups that we have so far.
125-
const groups = new Map<K, Subject<any>>();
165+
const groups = new Map<K, SubjectLike<any>>();
126166

127167
// Used for notifying all groups and the subscriber in the same way.
128168
const notify = (cb: (group: Observer<any>) => void) => {
@@ -153,15 +193,15 @@ export function groupBy<T, K, R>(
153193
let group = groups.get(key);
154194
if (!group) {
155195
// Create our group subject
156-
groups.set(key, (group = subjectSelector ? subjectSelector() : new Subject<any>()));
196+
groups.set(key, (group = connector ? connector() : new Subject<any>()));
157197

158198
// Emit the grouped observable. Note that we can't do a simple `asObservable()` here,
159199
// because the grouped observable has special semantics around reference counting
160200
// to ensure we don't sever our connection to the source prematurely.
161201
const grouped = createGroupedObservable(key, group);
162202
subscriber.next(grouped);
163203

164-
if (durationSelector) {
204+
if (duration) {
165205
const durationSubscriber = new OperatorSubscriber(
166206
// Providing the group here ensures that it is disposed of -- via `unsubscribe` --
167207
// wnen the duration subscription is torn down. That is important, because then
@@ -185,12 +225,12 @@ export function groupBy<T, K, R>(
185225
);
186226

187227
// Start our duration notifier.
188-
groupBySourceSubscriber.add(durationSelector(grouped).subscribe(durationSubscriber));
228+
groupBySourceSubscriber.add(innerFrom(duration(grouped)).subscribe(durationSubscriber));
189229
}
190230
}
191231

192232
// Send the value to our group.
193-
group.next(elementSelector ? elementSelector(value) : value);
233+
group.next(element ? element(value) : value);
194234
} catch (err) {
195235
handleError(err);
196236
}
@@ -214,7 +254,7 @@ export function groupBy<T, K, R>(
214254
* @param key The key of the group
215255
* @param groupSubject The subject that fuels the group
216256
*/
217-
function createGroupedObservable(key: K, groupSubject: Subject<any>) {
257+
function createGroupedObservable(key: K, groupSubject: SubjectLike<any>) {
218258
const result: any = new Observable<T>((groupSubscriber) => {
219259
groupBySourceSubscriber.activeGroups++;
220260
const innerSub = groupSubject.subscribe(groupSubscriber);

0 commit comments

Comments
 (0)