-
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
Remove secondary mappings for flattening operators #2929
Comments
I've not a lot to technically add to this discussion. |
@wmaurer hi, Wayne... generally it's not a bad practice. Overall, it's a little better, performance-wise, for older browsers and runtimes because it eliminates some closure and a subscription. However, that perf difference probably doesn't matter a ton in practice. The biggest thing about it is in situations where it might error (which is admittedly uncommon), you can't isolate the error to the inner observable. So in the two examples below one will stop, and the other will not: // With this one, the interval will die on the first error
Observable.interval(1000)
.mergeMap(
x => Observable.of(x),
y => {
if (y % 2 === 0) throw new Error(y)
return y
}
)
.catch(err => {
console.error(err);
return Observable.empty();
})
.subscribe(x => console.log(x));
/// With this one, the interval will live on. (usually desired)
Observable.interval(1000)
.mergeMap(
x => Observable.of(x)
.map(y => {
if (y % 2 === 0) throw new Error(y)
return y
})
.catch(err => {
console.error(err);
return Observable.empty();
})
)
.subscribe(x => console.log(x)); I mean, it's all down to use case. But the latter example above is (anecdotally) more common. |
More food for thought: Do we want to have symmetry with The signature there is: |
@benlesh FWIW, IxJS implements all according to spec for both |
I personally have never used the second selector for performance reasons, but I do tend to use (and suggest it) a lot. I personally find it ergonomically much nicer because it keeps my indentation level smaller. Also because the second selector doesn't deal with streams or iterables, I tend to treat it more like a pure selector that will never throw. I won't raise a fuss if the argument is for better alignment with the spec or reducing api surface area, but I don't think it should be removed for "disuse". |
Though I just realized with the "operator-as-a-library" paradigm if it was removed I could just publish |
Yeah, I definitely don't mean that to be the primary argument. The primary arguments are around: 1. alignment with the spec, and 2. library size. (although it's not a lot, it would add up across a few operators) |
Thanks for the explanation @benlesh. Like @paulpdaniels I've always treated the secondary selector as a pure function so I've never done anything "throwable" in there. |
With regards to that argument I made above, the problem isn't so much with the "throwable" part as it is in patterns like you'd see in redux-observable or ngrx, where you might have something that throws, moving the catch outside of the flattening operation will kill your entire stream, but catching it inside gets weird with that secondary map: // redux observable epic or ngrx effect
action$ => action$.ofType('LOAD_DATA')
.switchMap(() =>
makeAjaxCall()
.catch(err => {
console.log('ajax failed')
return Observable.of({ type: 'AJAX_ERROR', err })
}),
(responseOrError) =>
responseOrError.type === 'AJAX_ERROR'
? responseOrError
: { type: 'AJAX_RESPONSE', response: responseOrError }
) As you can see it gets ugly fast... But by just using a // redux observable epic or ngrx effect
action$ => action$.ofType('LOAD_DATA')
.switchMap(() =>
makeAjaxCall()
.map(response => ({ type: 'AJAX_RESPONSE', response }))
.catch(err => {
console.log('ajax failed')
return Observable.of({ type: 'AJAX_ERROR', err })
})
) |
... but honestly the biggest issues are library size and symmetry with the larger JavaScript ecosystem |
@benlesh thank-you, that's a very nice argument for not using the secondary selector. Now I'm off to review my ngrx/effects code! |
This is semi-relevant given that you discussed error handling above: I think the newly freed up second argument would be a good place for an "error selector", which allows you to flatmap errors in the source observable or in the projection to elements. Basically the same signature and semantics as This would alleviate some of the verbosity of doing |
GIven that IxJS and the spec are just |
I think one useful feature of selector functions is that they accepts parameters "unwrapped". For example:
or even easier with:
With
or this:
|
@martinsik I wasn't even thinking about |
This is now targeted at v8 to reduce breakage. |
For operators like
mergeMap
,switchMap
,concatMap
, etc, we have a secondary selector. It's not used a lot in practice that I've seen. Originally, it added a lot of value in terms of perf because it prevented a closure... however, in practice it makes it harder to isolate errors from observation chains, and in a world with Turbofan, I'm not sure the performance benefit is there. We could eliminate a good amount of code in the library written to support this.Use and Alternatives
Proposed Change
Eliminate the second argument to
mergeMap
,concatMap
,switchMap
, et. al. and instruct people to use.map
within the projection function if they want a secondary level of mapping.The text was updated successfully, but these errors were encountered: