-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(publishReplay): add selector function to publishReplay #2885
feat(publishReplay): add selector function to publishReplay #2885
Conversation
Generated by 🚫 dangerJS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A change to the one test, and a request that more test coverage be added.
spec/operators/publishReplay-spec.ts
Outdated
@@ -409,4 +409,15 @@ describe('Observable.prototype.publishReplay', () => { | |||
|
|||
published.connect(); | |||
}); | |||
|
|||
it('should mirror a simple source Observable with selector', () => { | |||
const selector = observable => observable.map(v => String.fromCharCode(96 + parseInt(v))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this one is a little too clever. :)
Can you use something more straightforward? A hash lookup or something?
spec/operators/publishReplay-spec.ts
Outdated
|
||
expectObservable(published).toBe(expected); | ||
expectSubscriptions(source.subscriptions).toBe(sourceSubs); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to add tests for:
- errors thrown in
selector
directly. - when
selector
returns an errored Observable (--a--b--#
) - when
selector
returns empty Observable - when
selector
returns never - when
selector
returns Observable.throw
Also, @martinsik, if you can explain your cool pixel art profile pic, that would be good too. What is it? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Last thing, we'll need to add function signature overrides for both operator/publishReplay
and operators/publishReplay
... they're slightly different. I've linked examples.
Thanks again!
src/operator/publishReplay.ts
Outdated
* @param scheduler | ||
* @return {ConnectableObservable<T>} | ||
* @return {Observable<T> | ConnectableObservable<T>} | ||
* @method publishReplay | ||
* @owner Observable | ||
*/ | ||
export function publishReplay<T>(this: Observable<T>, bufferSize: number = Number.POSITIVE_INFINITY, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like below, we'll need to add some function overloads for TypeScript users to keep this change from being breaking. Here's what the publish
counterpart to this file is doing...
Lines 7 to 9 in accbcd0
export function publish<T>(this: Observable<T>): ConnectableObservable<T>; | |
export function publish<T>(this: Observable<T>, selector: (source: Observable<T>) => Observable<T>): Observable<T>; | |
export function publish<T, R>(this: Observable<T>, selector: (source: Observable<T>) => Observable<R>): Observable<R>; |
src/operators/publishReplay.ts
Outdated
@@ -7,6 +7,8 @@ import { UnaryFunction } from '../interfaces'; | |||
|
|||
export function publishReplay<T>(bufferSize: number = Number.POSITIVE_INFINITY, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we'll need some function signature overloads for this so we don't break compatability for people expecting ConnectableObservable<T>
to be the only return type...
See what publish
is doing here for a better idea:
Lines 6 to 8 in accbcd0
export function publish<T>(): MonoTypeOperatorFunction<T>; | |
export function publish<T>(selector: MonoTypeOperatorFunction<T>): MonoTypeOperatorFunction<T>; | |
export function publish<T, R>(selector: OperatorFunction<T, R>): OperatorFunction<T, R>; |
LGTM.... @kwonoj? |
@benlesh Sorry, it's work in progress I just pushed it because It was too late yesterday :). I'm not sure about a few things there. btw, my avatar is a screenshot from this very old game https://en.wikipedia.org/wiki/Moonstone:_A_Hard_Days_Knight |
const published = source.publishReplay(1, Number.POSITIVE_INFINITY, selector); | ||
|
||
// The exception is thrown outside Rx chain (not as an error notification). | ||
expect(() => published.subscribe()).to.throw(error); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This exception is thrown outside the chain so I need to check it like this. This situations isn't tested in neither publish
nor multicast
(if I didn't miss anything) so I'm not sure I'm doing it right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OH GEEZ! I missed this... So what should happen is you should catch the error and send it down the error
path with observer.error(err)
. There are all sorts of examples of how to handle this around teh library.
Basically any user-supplied function needs to have it's errors caught and sent down the error channel of observation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I would expect multicast
to catch this error for me (for example like the map()
operator does) because that's where the selector is called: https://github.com/ReactiveX/rxjs/blob/master/src/operators/multicast.ts#L64
selectorOrScheduler?: IScheduler | OperatorFunction<T, R>, | ||
scheduler?: IScheduler): Observable<R> | ConnectableObservable<R> { | ||
|
||
return higherOrder(bufferSize, windowTime, selectorOrScheduler as any, scheduler)(this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couldn't find any other sane way to call higherOrder
. TypeScript didn't allow me just using:
higherOrder(bufferSize, windowTime, selectorOrScheduler, scheduler)
.. but I think this should work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will do, the other thing you can do is duplicate the logic that figures out what was passed from the other method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand, I didn't want to write the same logic twice so I'll leave it as it is.
scheduler?: IScheduler): UnaryFunction<Observable<T>, ConnectableObservable<T>> { | ||
return (source: Observable<T>) => multicast(new ReplaySubject<T>(bufferSize, windowTime, scheduler))(source) as ConnectableObservable<T>; | ||
/* tslint:disable:max-line-length */ | ||
export function publishReplay<T>(bufferSize?: number, windowTime?: number, scheduler?: IScheduler): UnaryFunction<Observable<T>, ConnectableObservable<T>>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The publish
operator defines MonoTypeOperatorFunction
return type but I don't think that's correct because this results into Observable<T>
while publish
should return ConnectableObservable<T>
.
https://github.com/ReactiveX/rxjs/blob/master/src/operators/publish.ts#L6-L8
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fine for now... we might need to address this more generally across the library.
export function publishReplay<T, R>(bufferSize?: number, windowTime?: number, selector?: OperatorFunction<T, R>, scheduler?: IScheduler): OperatorFunction<T, R>; | ||
/* tslint:enable:max-line-length */ | ||
|
||
export function publishReplay<T, R>(bufferSize?: number, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed all default values here and left it up to ReplaySubject
to decide.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add appropriate error handling.
const published = source.publishReplay(1, Number.POSITIVE_INFINITY, selector); | ||
|
||
// The exception is thrown outside Rx chain (not as an error notification). | ||
expect(() => published.subscribe()).to.throw(error); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OH GEEZ! I missed this... So what should happen is you should catch the error and send it down the error
path with observer.error(err)
. There are all sorts of examples of how to handle this around teh library.
Basically any user-supplied function needs to have it's errors caught and sent down the error channel of observation.
selectorOrScheduler?: IScheduler | OperatorFunction<T, R>, | ||
scheduler?: IScheduler): Observable<R> | ConnectableObservable<R> { | ||
|
||
return higherOrder(bufferSize, windowTime, selectorOrScheduler as any, scheduler)(this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will do, the other thing you can do is duplicate the logic that figures out what was passed from the other method.
scheduler?: IScheduler): UnaryFunction<Observable<T>, ConnectableObservable<T>> { | ||
return (source: Observable<T>) => multicast(new ReplaySubject<T>(bufferSize, windowTime, scheduler))(source) as ConnectableObservable<T>; | ||
/* tslint:disable:max-line-length */ | ||
export function publishReplay<T>(bufferSize?: number, windowTime?: number, scheduler?: IScheduler): UnaryFunction<Observable<T>, ConnectableObservable<T>>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fine for now... we might need to address this more generally across the library.
@benlesh I added a comment above that I'd expect |
Merged! Thanks @martinsik! |
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
Description:
This PR adds selector function to the
publishReplay
operator similarly topublish
.I didn't modify any existing tests. However, this could be probably a breaking change because I added the parameter before
scheduler
(I'm not sure this is a problem or not).Related issue (if exists):
#2844