What problems could arise when using variables that are external to an observable sequence inside the sequence?
For example:
updateCar(newCar: any): Observable<any> {
return of(...).pipe(
switchMap(
(value: any) => {
if (newCar.has4Wheels && value.lovePizza) {
// return a 4 wheel observable
} else {
// return a not 4 wheel observable
}
}
),
switchMap(
(value: any) => {
if (newCar.has4Windows && !value.lovePizza) {
// return a 4 window observable
} else {
// return a 2 window observable
}
}
)
);
}
I know the example above is weird, but i am just using it to ask the question.
What problems could arise with using newCar inside the sequence like being used in the example when it is external to the sequence? If there are no problems, great! Just feels like there is something wrong with this usage to me.
I think nothing (at least as far as you don't modify newCar).
It's true that you could rewrite this and start with for example the following:
of([whatever, newCar])
.pipe(
switchMap(([whatever, newCar]) => {
...
})
)
...
But I think this isn't necessary and would make things just more complicated without any real benefit.
Related
I'm using RxJS 7, and I would like to have a child generator (another Observable) emitting values based on the parent data.
I was already able to achieve this, but the solution I found is not efficient in terms of CPU usage because it needs to build a new RxJS pipeline for every parent item, and I believe I'm not using here the full potential RxJS has.
Constraints:
Emitted values from the Parent needs to be available to child generator;
Parent needs to know when child flow is done;
Child Observable can have many operators;
Efficient!
The working example:
const { from, mergeMap, reduce, lastValueFrom } = rxjs
function run() {
const parentData = [{ parentId: 1 }, { parentId: 2 }, { parentId: 3 }]
from(parentData)
.pipe(mergeMap((parent) => lastValueFrom(getChildFlow(parent))))
.subscribe((parent) => console.log(parent))
}
function getChildFlow(parent) {
return from(childGenerator(parent))
.pipe(reduce((acc, value) => {
acc.inner.push(value)
return acc
}, { inner: [] }))
}
async function* childGenerator(parentData) {
for await (const index of [1, 2, 3]) {
yield { childId: index, ...parentData }
}
}
run()
<script src="https://unpkg.com/rxjs#^7/dist/bundles/rxjs.umd.min.js"></script>
The reason I'm looking for a more efficient implementation is because it's intended for a data intensive system which can have millions of items flowing.
Questions!
Does RxJS provide some operator to cover this scenario in a more efficient implementation? I really dug RxJS's documentation and didn't found anything, but I may have missed it.
Would it be possible to reuse the flow on the above implementation? The tricky part here is that the child generator needs to have the parent data.
PS: Don't mind the implementation details of the code above, it's just an example of what I'm trying to achieve, and doesn't cover all the precautions and additional steps I have to justify the use-case.
I found the solution to my problem.
It required using mergeMap, groupBy, reduce and zip.
I'm not convinced it's the best solution, so if you find another approach for this that you think is more efficient, I will certainly upvote your answer and mark it as correct answer over mine.
const { from, mergeMap, tap, zip, map, groupBy, reduce } = rxjs
function run() {
const parent$ = from([{ parentId: 1 }, { parentId: 2 }, { parentId: 3 }])
.pipe(tap(doWhatever))
const reducer = reduce(accumulator, [])
const child$ = parent$
.pipe(mergeMap(childGenerator))
.pipe(tap(doWhatever))
.pipe(groupBy((p) => p.parentId))
.pipe(mergeMap((group$) => group$.pipe(reducer)))
zip([parent$, child$])
.pipe(map((results) => ({ ...results[0], inner: results[1] })))
.pipe(tap(doWhatever))
.subscribe(console.log)
}
function accumulator(acc, cur) {
return [...acc, cur]
}
function doWhatever() {}
async function* childGenerator(parentData) {
for await (const index of [1, 2, 3]) {
yield { childId: index, ...parentData }
}
}
run()
<script src="https://unpkg.com/rxjs#^7/dist/bundles/rxjs.umd.min.js"></script>
This is a pretty simple question.
How to implement subscriptions in graphql?
I'm asking specifically for when using graphql.js constructors like below ?
I could not find a clean/simple implementation.
There is another question here, but it deals with relay.js - i don't want to unnecessarily increase the nr of external dependencies in my app.
What i have:
module.exports = function (database){
return new GraphQLSchema(
{ query: RootQuery(database)
, mutation: RootMutation(database)
, subscription: RootSubscription(database) -- i can see this in graphiql - see below
}
);
}
function RootSubscription(database){
return new GraphQLObjectType(
{ name: "RootSubscriptionType"
, fields:
{ getCounterEvery2Seconds:
{ type: new GraphQLNonNull(GraphQLInt)
, args :
{ id: { type: GraphQLString }
}
, subscribe(parent, args, context){
// this subscribe function is never called .. why?
const iterator = simpleIterator()
return iterator
}
}
}
}
)
}
I learned that i need a subscribe() which must return an iterator from this github issue.
And here is a simple async iterator. All this iterator does - is to increase and return the counter every 2 seconds. When it reaches 10 it stops.
function simpleIterator(){
return {
[ Symbol.asyncIterator ]: () => {
let i = 0
return {
next: async function(){
i++
await delay(2000)
if(i > 10){
return { done: true }
}
return {
value: i,
done: false
}
}
}
}
}
}
When i run the graphiql subscription, it returns null for some reason:
I'm piecing together code from multiple sources - wasting time and hacking it basically. Can you help me figure this one out?
Subscriptions are such a big feature, where are they properly documented? Where is that snippet of code which you just copy paste - like queries are for example - look here.
Also, i can't use an example where the schema is separate - as a string/from a file. I already created my schema as javascript constructors. Now since im trying to add subscriptions i can't just move back to using a schema as a string. Requires rewriting the entire project. Or can i actually have both? Thanks :)
RxJS v4 used to have an Observable.transduce method which took a transducer. This allowed the use of library-independent transducer operators which had major performance benefits in the past.
Sources
https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/transduce.md
https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/transducers.md
https://medium.com/front-end-hacking/rxjs-transducers-vs-method-chaining-performance-87561cf4ce65
https://github.com/ReactiveX/rxjs/pull/1323
RxJS v5.5 and v6 have pipeable operators and v6 removed method chaining. Because of this, I assumed RxJS operators were standard transducers. Looking through the source code, that doesn't seem to be the case.
RxJS v6 operators function like a transducer where each value is passed entirely through the chain before the next value goes through, but RxJS v6 operators aren't using the standard transducer methods I've seen in other libraries meaning, I don't think they're portable.
The whole thing about transducers is they don't know anything about the collection itself. Instead of writing 100 operators specifically for observables, you could write 100 operators universally able to be applied to any collection or stream type.
Is .pipe unanimous with .transduce or was this method completely removed in RxJS v5?
I had the exact same question and could not find the answer anywhere. Yes you can pipe, but I believe that would create intermediary observables for each operator. I don't know for sure though, it would be about reading the code.
So I came up with my own transduce operator :
function transformForObserver(o) {
return {
"##transducer/init": function() {
return o;
},
"##transducer/step": function(obs, input) {
return obs.next(input);
},
"##transducer/result": function(obs) {
return obs.complete();
}
};
}
const transduce = (obs, transducer) => {
const xform = transducer(transformForObserver);
return Observable.create(o => {
return obs.subscribe({
next: x => {
const res = tryCatch(
xform["##transducer/step"],
err => {
console.error(`Error occurred in transducer/step!`, err);
return err;
}
)(xform, o, x);
if (res instanceof Error) { o.error(res); }
},
error: err => {
console.error(`Error occurred in observable passed to Rx transduce fn!`, err);
o.error(err);
},
complete: () => {o.complete();}
});
});
}
Haven't tested it yet, will post soon about it if there is interest.
Update : I forked jslongser's tranducers library and included such transducers in it. Fork is https://github.com/brucou/transducers.js, and the function is transduceLazyObservable. Cf. tests for example of use.
I'm trying to set up an RxJs recipe to perform some steps and do operations conditionally based on the result of some subscriptions.
The pseudo-code is:
if (trySocialSign() succeeds) {
if (tryGetUserFromDatabase() succeeds) {
do.some.stuff
return
} else {
do.other.stuff
}
}
Right now I have this ugly function that works but I'm wondering if there's a prettier way using pipes and maps and other rxjs operators to be able to achieve the same effect in a more idiomatic way with less nesting. Can someone please help me?
this.auth.getCurrentUserAsync().subscribe(
(u: EasyAuthUser) => {
this.currentUser = u;
this.users.getUser().subscribe(
(user: User) => {
this.onExistingUserSignIn.emit(user);
},
(err: HttpErrorResponse) => {
if (redirected) {
this.onSignIn.emit(u);
}
}
);
},
(err: HttpErrorResponse) => {this.handleHttpError(err)}
)
You could do it like the following code (I didn't test it for obvious reasons). All side-effects are performed only from let. There's also one catchError that will suppress the inner error.
this.auth.getCurrentUserAsync().pipe(
let((u: EasyAuthUser) => this.currentUser = u),
mergeMap((u: EasyAuthUser) => this.users.getUser().pipe(
let(
(user: User) => this.onExistingUserSignIn.emit(user),
(err: HttpErrorResponse) => {
if (redirected) {
this.onSignIn.emit(u);
}
}
),
catchError(e => empty()), // Maybe you don't even want this `catchError`
))
).subscribe(
user => ...,
(err: HttpErrorResponse) => this.handleHttpError(err),
);
I have a selector:
const mySelector = createSelector(
selectorA,
selectorB,
(a, b) => ({
field1: a.field1,
field2: b.field2
})
)
I know the selector is evaluated when any of its inputs change.
In my use case, I need to control "mySelector" by a third selector "controlSelector", in the way that:
if "controlSelector" is false, "mySelector" does not evaluate a new value even in the case "selectorA" and/or "selectorB" changes, and returns the memoized value
if "controlSelector" is true, "mySelector" behaves normally.
Any suggestions?
Selectors are pure functions..its will recalculate when the input arguments are changed.
For your case its better to have another state/object to store the previous iteration values.
You can pass that as selector and based on controlSelector value you can decide what you can return.
state : {
previousObj: {
...
}
}
const prevSelector = createSelector(
...,
(state) => state.previousObj
)
const controlSelector = createSelector(...);
const mySelector = createSelector(
controlSelector,
prevSelector,
selectorA,
selectorB,
(control, a, b) => {
if(control) {
return prevSelector.previousObj
} else {
return {
field1: a.field1,
field2: b.field2
};
}
}
)
Sorry for the delay...
I have finally solved the issue not using NGRX selectors to build up those "higher selectors" and creating a class with functions that use combineLatest, filter, map and starWith
getPendingTasks(): Observable<PendingTask[]> {
return combineLatest(
this.localStore$.select(fromUISelectors.getEnabled),
this.localStore$.select(fromUISelectors.getShowSchoolHeadMasterView),
this.memStore$.select(fromPendingTaskSelectors.getAll)).pipe(
filter(([enabled, shmView, tasks]) => enabled),
map(([enabled, shmView, tasks]) => {
console.log('getPendingTasks');
return tasks.filter(task => task.onlyForSchoolHeadMaster === shmView);
}),
startWith([])
);
}
Keeping the NGRX selectors simple and doing the heavy lifting (nothing of that in this example, though) in this kind of "selectors":
- will generate an initial default value (startWith)
- will not generate new value while filter condition fails (that is, when not enabled, any changes in the other observables do not fire a new value of this observable)