How to avoid an infinite loop with Observables? - javascript

I have the following code:
ngOnInit(): void
{
const source = this.categoryService.getCategories();
console.log(source instanceof Observable);
const example = source.map((categor) => categor.map((categories) => {
const links = this.categoryService.countCategoryLinks(categories.id);
const aff = example.merge(links).subscribe(linke => console.log(linke));
return categories.id
}));
}
where getCategories() returns an observable.
On each item of this Observable, I get categories.id field to run another method called countCategoryLinks(categories.id) that also returns an Observable().
My problem is that : I only have 3 categories (ok), the countCategoryLinks() returns 3 items (ok) but the code above shows an infinite loop in the console.
Both methods started "manually" for testing purpose do not show any loop.
The problem really comes from the code above.
Any idea ?
Thanks in advance and Regards

example.merge(links) <= you are using an observable created by the map callback in the map callback which would cause recursion (ie. loop back into itself). This is where it pays to use proper indention as it is easier to see.
ngOnInit(): void {
const source = this.categoryService.getCategories();
console.log(source instanceof Observable);
const example = source.map((categor) => categor.map((categories) => {
const links = this.categoryService.countCategoryLinks(categories.id);
// this line is the issue. You are using variable example which is being created by the callback this line of code is in
const aff = example.merge(links).subscribe(linke => console.log(linke));
return categories.id
}));
}
I am thinking maybe you did not mean to still be inside of map at this point?

Related

Custom react hooks infinite recursion, no recusrion break because of Rule of hooks

I've encountered interesting problem with custom react hooks, rule of hooks, and recursion. I would like to know if I'm doing something wrong. Thanks for you time.
I have a custom hook useServerData(entityIds). This hook internaly calls (depends on) other custom hook useFilters(entityIds). This hook cyclickly depends on useServerData(shorterEntityIds). Maybe it seems like a logical defect, but it is just a recurion because shorterEntityIds are strictly shorter array. Without rule of hooks, I would wrote recursion break like:
let filters = [];
if (shorterEntityIds.length > 0) {
filters = useFilters(shorterEntityIds)
}
But it is prohibited by rule of hooks (and I understand why). Without the break JavaScript throws Maximum call stack size exceeded.
How would you solve this? How to break the cycle? What is a React idiomatic way?
Thank you.
EDIT: As Bergi requested I'm adding more specific code for this.
// Returns dictionary where key is given filterId and value is QueryResult<FilterRange>
// Where returned FilterRange depends on Filter and prescending Filters for each Filter
export const useFilterRanges = (filterIds) => {
const filtersById = useFilters(filterIds);
const prescendingQueryFiltersById = usePrescendingQueryFilters(filterIds);
const filterRanges = useQueries(filterIds.map((filterId) => {
const filter = filtersById[filterId];
const prescendingFilters = prescendingQueryFiltersById[filterId];
return fetchFilterRange(filter, prescendingFilters);
}));
return zipObject(filterIds, filterRanges);
};
// Returns dictionary where key is given filterId and value is Array<QueryFilter>
// Where returned Array<QueryFilter> depends on internal state (order of Filters) and QueryFilters
export const usePrescendingQueryFilters = (filterIds) => {
const allFilterIds = useAllFilterIds();
const index = findLastIndex(allFilterIds, (filterId) => includes(filterIds, filterId));
// allPrecendingIds are always at least one item shorter than filterIds
const allPrecendingIds = take(allFilterIds, index);
const queryFiltersById = useQueryFilters(allPrecendingIds);
return chain(filterIds)
.keyBy()
.mapValues((filterId) => {
const index = indexOf(allFilterIds, filterId);
const precendingIds = take(allFilterIds, index);
const queryFilters = precendingIds.map((id) => queryFiltersById[id]);
return queryFilters.flatMap((queryFilter) => queryFilter ?? []);
})
.value();
};
// Returns dictionary where key is given filterId and value is QueryFilter
// Where returned QueryFilter depends on QueryResult<FilterRange> and Filter
export const useQueryFilters = (filterIds) => {
const filtersById = useFilters(filterIds);
const rangesById = useFilterRanges(filterIds);
return chain(filterIds)
.keyBy()
.mapValues((filterId) => {
const filter = filtersById[filterId];
const range = rangesById[filterId];
return constructQueryFilters(filter, range);
})
.value();
};
Note the code is simplified (I've doublechecked but it can contain some typos) and the real project cycle is even bigger, but I believe this is kind of minimal meaningful example. :)

If a function is only used in another function should I keep it inside or outside it?

Like in the subject. I have a function like below and I have quite a bit of helping functions declared within a function (twice as much than in the example) because it's the only one using them.
My question is: should I extract those helping functions outside the function to maintain rule "Function should do one job and do it well" or it should be within? I also read about that higher level functions should be higher for better readability, but it somehow doesn't work (shouldn't hoisting make it work?).
const queryThings = async (body = defaultBody) => {
try {
(...)
// helping functions
const isNonTestDeal = obj => (...)
const isNonEmpty = obj => (...)
const replaceHTMLEntity = obj => (...)
const extractCountries = o => (...)
const queried = await query(...) // that one is outside this function
const cases = queriedCases
.filter(isNonTestDeal)
.map(obj => {
let countries = [(...)]
.filter(isNonEmpty)
.map(replaceHTMLEntity)
.map(extractCountries)
let data = {
(...)
}
return data
})
.filter(obj => (...))
.sort((a,b) => a.d - b.d)
.slice(0, 45) // node has problem with sending data of more than 8KB
return cases
} catch (error) {
console.log(error)
}
}
If you declare the function outside, and only use it in one function, then you cause namespace pollution. (What is namespace pollution?) Thus, I would recommend keeping it inside. Also, if you do so, it is easier to read as well, since it will be closer to the code where it is used.
To address your question about hoisting, it only works if you declare your function without assigning it to a variable.
i think when you write function in other function the memory use is better than write out of function
but you can't use in another function it is local function and it isn't public function

Polling with RxJS that can recover missed events

I am trying to use RxJS to poll for events. However, I only have access to one function, which is getEvent(). I can do 2 things with the getEvent function:
getEvent("latest") — this will give me the latest event object
getEvent(eventId) - I pass in an integer and it will give me the event object corresponding to the eventId.
Event IDs always increment from 0, but the problem is, if my polling interval isn't small enough, I might miss events.
For example, if I do a getEvent("latest") and I get an event that has an ID of 1, that's great. But if the next time I call it, I get an ID of 3, I know that I missed an event.
In this case, I want to use a higher-order observable to call getEvent(2) and getEvent(3) so that the consumer of the stream I am creating won't have to worry about missing an event.
Right now, all I have is something like this:
timer(0, 500).pipe(
concatMap(() => from(getEvent("latest"))
)
For some context, I'm working off of this blogpost: https://itnext.io/polling-using-rxjs-b56cd3531815
Using expand to recursively call GET fits here perfectly. Here is an example with DEMO:
const source = timer(0, 2000)
const _stream = new Subject();
const stream = _stream.asObservable();
const s1 = source.pipe(tap(random)).subscribe()
const sub = stream.pipe(
startWith(0),
pairwise(),
concatMap((v: Array<number>) => {
let missing = v[1] - v[0];
return missing ? getMissing(v[0], missing) : EMPTY
})
).subscribe(console.log)
function getMissing(start, count) {
return getById(start).pipe(
expand(id => getById(id+1)),
take(count)
)
}
// helper functions for DEMO
let i = 1;
function random() {. // THIS IS YOUR getEvent('latest')
if (i < 10) {
i+=2;
_stream.next(i
// (Math.floor(Math.random() * 8))
)
}
}
function getById(id) {. // THIS IS YOUR getEvent(eventId)
return of(id).pipe(delay(1000)) // delay to mimic network
}

RXJS: assigning and then reusing value between operators

Suppose I have a function GetUserRecommendedSongs. It does the following:
It presents the user with a dialog: what is your mood today?
(happy\gloomy\nostalgic)
Using the result it calls a service GetUserRecommendationsByMood.
It then returns a result for example: {mood: "nostalgic", songIds:
[12, 25]};
The caller of this function (possibly several ui components) would
use the result to play the songs under the title "so you feel ${result.mood} today?"
The problem is I use the mood twice: to get the recommendations, and in the final result.
With async\await I would do:
const requiredMood = await ShowRequiredMoodDialog();
//handle cancellation e.g. if(!mood)
let recommendedSongs = await GetUserRecommendations(mood);
return {mood, recommendedSongs};
However with rxjs I was only able to come up with the following:
let mood$ = ShowRequiredMoodDialog().pipe(share) //has to be shared so we don't show the dialog twice
let recommendedSongs$ = mood$.pipe(switchMap((mood)=> GetUserRecommendations(mood)));
return forkJoin(mood$, recommendedSongs$) //with some selector\map to turn into object
(note to reader: don't use this as a reference to rxjs, as I did not test this code)
This code is quite complicated. Can it be simplified?
The only different way that comes to my mind is like this but I don't know which one is more readable:
mood$.pipe(
switchMap(mood => GetUserRecommendations(mood).pipe(
map(recommendedSongs => [mood, recommendedSongs]),
),
)
What about this one?
let mood$ = ShowRequiredMoodDialog().pipe(share());
let recommend = (mood) => {
return { mood, songs: GetUserRecommendations(mood) };
};
let recommendedSongs$ = mood$.pipe(switchMap(recommend));
return recommendedSongs$;

looking for a cleaner way to use scan in rxjs

So I have seen one useful way of using scan where you map a function to the stream to update an initial value.
const initialState = false;
const notThis = (x) => !x;
const toggle = clicks.map(()=> notThis)
.startWith(initialState)
.scan((acc,curr)=> curr(acc))
But if I need the values from clicks I know I can write a partial
const initialState = {klass:'',bool:false};
const toggle = clicks
.map((y)=> {
return (x) => {
let klass = y.target.className;
let bool = !x.bool;
return ({klass, bool});
};
})
.startWith(initialState)
.scan((acc,curr)=> curr(acc));
This could be very useful if I am merging several streams, but it also seem like it could be overly complicated. Is there a better way to accomplish passing data and functions down the stream? Here is a bin of this example link
Well, you should ask yourself if you need to pass down functions. I made your code a bit simpler:
https://jsbin.com/timogitafo/1/edit?js,console,output
Or an even simpler one: https://jsbin.com/giqoduqowi/1/edit?js,console,output

Categories