debounceTime() over API calls using Rxjs - javascript

I am trying to understand rxjs and got stuck at a debounceTime(n /* ms */) experiment.
public debounceTime(dueTime: number, scheduler: Scheduler): Observable
Emits a value from the source Observable only after a particular time span has passed without another source emission.
source
My code:
function fakeAPI() {
return new Rx.Observable(observer => {
const root = 'https://jsonplaceholder.typicode.com'
$.ajax({
url: root + '/posts/1',
method: 'GET'
}).then(function(data) {
observer.next(data)
}).fail(function(err) {
observer.error(err)
})
return ()=>{
observer.complete()
console.log('unsubscribed!')
}
})
}
const fakeObserver = fakeAPI()
$('#buttonText').click(()=>{
fakeObserver
.debounceTime(10000)
.subscribe(() => {
return {
next(item) {
console.log('received: ', item.id)
},
error(err) {
console.log('error:', err)
},
complete() {
console.log('completed!')
}
}
}());
})
My expectation: Even with N number of clicks in the given amount of time, the API call would only be made once. Instead, it seems like it waits for the given time and then all the N clicks result in an API call.
What am I doing wrong?
As per the docs, debounceTime(n) is supposed to discard previous pending delayed emissions if a new value arrives on the source.
Here is a JSBin link

As per the docs, debounceTime(n) is supposed to discard previous pending delayed emissions if a new value arrives on the source.
It's true but on each click:
You create new subscription
It calls api
Api returns result
debounceTime waits 10s (nothing happens because observerable returned by fakeObserver emits only once)
You log the result
You need to convert your clicks in observable to implement what you want:
Rx.Observable.fromEvent(document.getElementById('buttonText'), 'click')
Check jsBin

Related

How to subscribe to an observable once?

My function access() needs to subscribe once, each and every call.
In the snippet below, $valueChanges emits data to each change made. Calling access() without const $ = ... and $.unsubscribe(), $valueChanges observable emits unnecessary streams of values.
Is there an rxjs operator/function that emits once in subscription inside a function? Even if the function is called multiple times the subscription emits once?
access() {
const $ = $valueChanges.pipe(
map((res) =>
...
),
).subscribe((res) => {
...
$.unsubscribe();
});
}
You can consider using the take() operator, and emit only the first value before completing.
According to the documentation, the take operator
Emit provided number of values before completing.
This is how you can use it:
access() {
valueChanges
.pipe(
map((res) =>
...
),
take(1),
).subscribe((res) => {
...
});
}
Try shareReply(1). Then the original stream will be called only once and its emit will be shared with all subscribers. If the stream emits 2nd time - the update will go to all subscribers too.
access() {
const $ = $valueChanges.pipe(
map((res) =>
...
),
// take(1), // in case if you need just 1 emit from it.
shareReply(1), // in case if you don't want to trigger `$valueChanges` on every subscription.
).subscribe((res) => {
...
// $.unsubscribe(); // it's useless in case of `take(1)`.
});
}

Vuex update state

When I'm calling a Vuex action and perform an axios request, it's not updating all my localStorage item, it's very strange.
My axios request could return objects or an empty collection and I would like to update all the time my userDailyFoods item. I console log my mutation and getter and it works but for the update of the localStorage item it's bugging. Sometimes I get the result, sometimes not.
What am I doing wrong ?
action:
updateUserDailyFoods(context, data) {
let date = data.data
if (date) {
axios.get(`/user/daily/food/${context.getters.user.id}/${date}/`).then(r => {
context.commit('userDailyFoods', {
userDailyFoods: r.data.data
});
localStorage.setItem('userDailyFoods', JSON.stringify(r.data.data));
}).catch(e => {
console.log(e)
})
}
console.log(localStorage.getItem('userDailyFoods'));
}
mutation:
userDailyFoods (state, payload) {
if(payload) {
state.userDailyFoods = payload.userDailyFoods
}
},
getter:
userDailyFoods(state){
return state.userDailyFoods
}
state:
userDailyFoods: JSON.parse(localStorage.getItem('userDailyFoods')) || [],
UPDATED
when i go to my view component
computed: {
userDailyFoods() {
return this.$store.state.userDailyFoods
},
}
and I console log my action:
console.log(this.userDailyFoods)
then the result is updated after two clicks, not only one.
getDaySelected(day) {
var day = moment(day).format('YYYY-MM-DD');
this.$store.dispatch('updateUserDailyFoods', {
data: day
})
console.log(this.userDailyFoods)
this.$parent.$data.foods = this.userDailyFoods
}
(Answer as requested) The value is probably being set in localStorage every time, but you're trying to log it before the async Axios operation has completed. Move the console.log into the then block:
.then(r => {
context.commit('userDailyFoods', {userDailyFoods: r.data.data});
localStorage.setItem('userDailyFoods', JSON.stringify(r.data.data));
console.log(localStorage.getItem('userDailyFoods'));
})
Axios (and AJAX in general) is asynchronous which means that the operation begins separately from the normal synchronous flow of code. After it begins, program execution immediately resumes, often before the async operation is complete. At the time you are logging to the console, there is no guarantee that this async promise is resolved yet.
As an aside, there is no need for that getter, which you can think of as a computed that isn't computing anything.

Handling parallel nested observables in RxJS

I'm working on cleaning up nested parallel observables in my project and I am having some issues wrapping my head around handling the observable events. Here's one situation in my music application where I load the Playlist page and need to load the playlist data, information about the user, and if the user is following the playlist:
Route Observable (returns parameters from URL, I need to extract the playlistId)
Playlist Observable (needs playlistId and returns PlaylistObject and resolves)
User Observable (returns UserObject)
IsUserFollowing Observable (needs playlistId and UserObject.id and returns T/F if user is following the playlist)
In summary, I receive the route parameter from Angular's Route observable, I need to fire two other observables in parallel, the Playlist Observable and User Observable. The Playlist Observable can resolve once the playlist data is returned, but the User Observable needs to continue on to another observable, IsFollowingUser, until it can resolve.
Here is my current (bad) implementation with nested observables:
this.route.params.pipe(
first()
).subscribe((params: Params) => {
this.playlistId = parseInt(params["playlistId"]);
if (this.playlistId) {
this.playlistGQL.watch({
id: this.playlistId,
}).valueChanges.pipe(
takeUntil(this.ngUnsubscribe)
).subscribe((response) => {
this.playlist = response; // use this to display playlist information on HTML page
this.playlistTracks = response.tracks; //loads tracks into child component
});
this.auth.user$.pipe(
filter(stream => stream != null)
).subscribe((user) => {
this.user = user;
this.userService.isFollowingPlaylist(this.user.id, this.playlistId).subscribe((response) => {
this.following = response; //returns a T/F value that I use to display a follow button
})
})
}
This works but obviously does not follow RxJS best practices. What is the best way to clean up nested parallel observables like this?
This is something I would do although this may not be the most optimal but could be better than before.
I will assume the previous code block is in an ngOnInit.
async ngOnInit() {
// get the params as if it is a promise
const params: Params = await this.route.params.pipe(
first()
).toPromise();
this.playlistId = parseInt(params["playlistId"]);
if (this.playlistId) {
// can name these functions to whatever you like (better names)
this.reactToPlaylistGQL();
this.reactToUser();
}
}
private reactToPlaylistGQL() {
this.playListGQL.watch({
id: this.playListId,
}).valueChanges.pipe(
takeUntil(this.ngUnsubscribe)
).subscribe(response => {
this.playlist = response;
this.playlistTracks = response.tracks;
});
}
private reactToUser() {
this.auth.user$.pipe(
filter(stream => stream != null),
// switch over to the isFollowingPlaylist observable
switchMap(user => {
this.user = user;
return this.userService.isFollowingPlaylist(this.user.id, this.playListId);
}),
// consider putting a takeUntil or something of that like to unsubscribe from this stream
).subscribe(response => this.following = response);
}
I find naming the functions in the if (this.playlistId) block to human friendly names makes it unnecessary to write comments (the descriptive function name says what it should do) and looks cleaner than putting a blob of an RxJS stream.
I wrote this all freehand so there might be some typos.

Angular RXJS retry a task based on a value

Ok, so I have a service that checks to see if a particular 3rd party JS plugin has loaded. I want to listen in for when it has entered the DOM, meanwhile it is in an undefined state. How do I do that? So far I have tried using a subject and retrying periodically but I can't get it to work:
$apiReady: Subject<boolean> = new Subject();
RegisterOnNewDocumentLoadedOnDocuViewareAPIReady(reControl: any): any {
this.$apiReady.asObservable().subscribe((isReady: boolean) => {
if (isReady) {
//Do something
return of(isReady);
}
})
let IsreControlInitialized = ThirdPartyAPI.IsInitialized(reControl);
if (IsreControlInitialized) {
this.$apiReady.next(true);
}
return throwError(false);
}
Then in the component:
this._apiService.RegisterOnAPIReady(this.elementID).pipe(
retryWhen(error => {
return error.pipe(delay(2000)); //<---- Doesn't work
})).subscribe((response: boolean) => {
if (response) {
//Do some stuff
}
});
My intentions were to check if the API element had been loaded, if not retry in 2 seconds but this doesn't work, can anyone help?
Throwing and catching an error until some condition is met is a little counter-intuitive to me.
My approach consists of using the interval operator along with takeUntil operator.
apiReady = new Subject();
interval(2000) // Check every 2s
.pipe(
map(() => this.isApiReady())
takeUntil(this.apiReady)
)
.subscribe(isReady => {
if (isReady) {
this.apiReady.next();
this.apiReady.complete();
}
})

Cancel pending API calls in Restangular

I have API service:
var SearchSuggestionApi = function (Restangular) {
return {
getSuggestion: function (keyword) {
return Restangular.one('search').customGET(null, {keyword:keyword});
}
};
};
SearchSuggestionApi.$inject = [
'Restangular'
];
I have controller to call this API:
vm.getSuggestion = function (keyword) {
SearchSuggestionApi.getSuggestion(keyword)
.then(
function (data) {
vm.listData = data;
}, function (error) {
console.log(error);
});
};
My problem is when I call vm.getSuggestion(keyword) two or many time (must call than one time). Such as:
vm.getSuggestion('a'); // => Return a array with many object in this
vm.getSuggestion('a b');// => Return empty array
Because vm.getSuggestion('a') return many data, it will finish after vm.getSuggestion('a b'). So vm.listData is [{object1}, {object2}, ...], but I want to vm.listData is [] (response data of the last function).
How can to cancel pending API call in first function when I call seconds function or another ways to get the last response data and set for vm.listData.
I researched some articles about cancel pending API calls, but it not help me about my problem.
Thanks for your help :)
There are various ways of solving this:
You can simply check in your then callback whether the value received is still current:
vm.getSuggestion = function (keyword) {
SearchSuggestionApi.getSuggestion(keyword)
.then(
function (data) {
if (vm.keyword === keyword) {
vm.listData = data;
}
}, function (error) {
console.log(error);
});
};
You can cancel the request by specifying a timeout promise
If you are solving this problem often, you might wish to replace the promise by an RxJS observable stream with the appropriate operators. This is the cleanest solution, but does require an additional library.

Categories

Resources