I've written this piece of code,
timer(0, 100)
.pipe(
windowTime(1000),
take(3),
flatMap( value => value.pipe(toArray())))
I don't understand why in my subscribe I only get 2 values. Whatever n take I always get n-1 values in my subscribe.
Could you explain to me why ?
Edit: I suspect a bug therefore I opened a bug.
Interesting problem!
So, windowTime(1000) will emit a new window every 1000ms. But what exactly is a window? A window is Subject instance.
windowTime can manage multiple windows, but when you're only providing its first argument(called windowTimeSpan), there will be only one active window. What this means is that after windowTimeSpan ms pass, the current window will be closed and a new one will be created and pushed into the stream.
When a window is closed, it means that it will send a complete notification. This is a very important aspect.
When you subscribe, a window is going to be created immediately and pushed into the stream.
By doing flatMap(value => value.pipe(toArray()))), you can register observers for the current window(subject). It's semantically the same as subject.pipe(toArray()).subscribe(subscriber)
Why does it behave this way?
timer(0, 100)
.pipe(
// Emit a value(window) every 1000ms and close(complete) the prev one
windowTime(1000),
take(3),
flatMap( value => value.pipe(toArray()))
)
Firstly, let's have a look at flatMap. flatMap is the same as mergeMap. What mergeMap does is to manage a number(concurrent, defaults to INFINITY) of inner observables. An inner observable is being tracked until it completes.
What toArray does it to accumulate values until its source completes. In this case, until the current subject(window) completes. This happens when a window is closed, more specifically, when 1000ms pass.
So, as delineated before, a window will be created immediately on subscription.
After 0ms a value(0) comes in, after 100ms another value(1) and so forth until value 9 comes in. In the meanwhile all these values were collected by toArray. So 9's arrival also marks 1000ms, that is also when the current window will be closed(will emit a complete notification). When this happens, toArray will receive the notification and will send the collected values to the data consumer.
After this a new window is created(second value for take(3)). Then, value 10 comes in, then value 11 and so forth until 19, which marks another 1000ms, which will cause the current window to complete and a new one to be created. But this new window would represent the 3rd value for take(3). This means that take will unsubscribe from its source and will emit a complete notification.
As a result, the source will not be able to receive any other values, so this should explain why you're only getting 2 arrays.
Related
I am using the following code to basically ensure that I get a result from an Observable
this.authenticationService.isLoggedIn
.pipe(
take(1),
defaultIfEmpty(false)
)
.subscribe(result => return result);
Somehow it feels wrong to me, maybe because it seems sort of procedural.
Is this method okay? Will this get me in trouble in any way?
If in your code it's fine that this.authenticationService.isLoggedIn completes without an emit - then the code in your question is fine too.
If this.authenticationService.isLoggedIn emits anything at some point of time and completes after - then defaultIfEmpty is redundant.
It all depends on what isLoggedIn does.
It is clear that isLoggedIn returns an Observable.
Now, and Observable can do just 3 things
it can notify, i.e. emit, some data for consumption of Observers which are subscribed
it can raise an error
it can complete
So the first question is: how many times can isLoggedIn notify? Is it just one shot or is it a stream of notifications? If it can emit just one value and then complete, than the take(1) operator is useless.
But there is also the case that isLoggedIn never notifies and just completes. In this case a notification would never be signaled by the observer to its subscriber. Using defaultIfEmpty operator ensures that something is notified even in this case.
So, reading your code I understand that isLoggedIn can behave in these 2 ways
Emit more than once but you are interested only in the first notification
Never notify and just complete, in which case you want false to be returned
If this is not true, it may be the case that your code can be simplified.
I have a scenario where 1 observable listens for events, which should then fire another asynchrounous event, and wait before it runs the next item in the source Observable.
The first observable can be triggered much faster than the the async event, and it must wait for the async event to complete before it takes another item from the 1st observable.
So.. essentially I need to set up a 'queue' from the first observable (as I cant lose the data from source 1)
Source 2 should take 1 item at a time from the queue, run it, remove the item from the queue, and go onto the next item in the queue .
src1- --ev1---ev2---ev3----ev4---ev5--ev6---
src2- --ev1------------ev2-------------ev3--------ev4-------ev5------ev6
--------------async-----------async---------async------async------asyc
I was looking at the RX docs and it seems that pausibleBuffered could be a solution but I noticed it has been removed in RX5, which is what I am using. Can someone give advice as the right way to accomplish this ?
Thanks!
You can use mergeScan to run async operations one by one because it needs the previous async operation’s result to run an async operation.
const src2 = src1.mergeScan((_, value) => doSomething(value));
http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-mergeScan
Below is code from the Ngrx example: https://github.com/ngrx/example-app/blob/master/src/effects/book.ts My question is why in the first #Effect, it uses switchMap while the others use mergeMap. Is that because the first #Effect is dealing with network, and with the switchMap you can cancel the previous network request if it's running?
#Effect() search$ = this.updates$
.whenAction(BookActions.SEARCH)
.map<string>(toPayload)
.filter(query => query !== '')
.switchMap(query => this.googleBooks.searchBooks(query)
.map(books => this.bookActions.searchComplete(books))
.catch(() => Observable.of(this.bookActions.searchComplete([])))
);
#Effect() clearSearch$ = this.updates$
.whenAction(BookActions.SEARCH)
.map<string>(toPayload)
.filter(query => query === '')
.mapTo(this.bookActions.searchComplete([]));
#Effect() addBookToCollection$ = this.updates$
.whenAction(BookActions.ADD_TO_COLLECTION)
.map<Book>(toPayload)
.mergeMap(book => this.db.insert('books', [ book ])
.mapTo(this.bookActions.addToCollectionSuccess(book))
.catch(() => Observable.of(
this.bookActions.addToCollectionFail(book)
))
);
#Effect() removeBookFromCollection$ = this.updates$
.whenAction(BookActions.REMOVE_FROM_COLLECTION)
.map<Book>(toPayload)
.mergeMap(book => this.db.executeWrite('books', 'delete', [ book.id ])
.mapTo(this.bookActions.removeFromCollectionSuccess(book))
.catch(() => Observable.of(
this.bookActions.removeFromCollectionFail(book)
))
);
}
You are correct; switchMap will unsubscribe from the Observable returned by its project argument as soon as it has invoked the project function again to produce a new Observable.
RxJs is incredibly powerful and dense, but its high level of abstraction can sometimes make code hard to understand. Let me debunk the marble diagrams and docs given by #Andy Hole a little and bring them up to date. You may find the marble syntax reference highly valuable to better understand rxjs operators from their tests (at least I found this missing/not highlighted enough in the official docs).
mergeMap
The first line in the diagram is the source Observable which emits (1,3,5) at different times. The second line in the diagram is the prototypical Observable returned by the project function i => ... passed to the .mergeMap() operator.
When the source Observable emits the item 1, mergeMap() invokes the project function with i=1. The returned Observable will emit 10 three times, every 10 frames (see marble syntax reference). The same happens when the source Observable emits item 3 and the project function creates an Observable that emits 30 three times. Note that the result of mergeMap() contains all three elements generated by each Observable returned from project.
switchMap
This is different with switchMap(), which will unsubscribe from the Observable returned by project as soon as it has invoked it again on a new element. The marble diagram indicates this with the missing third 30 item in the output Observable.
In the example you have given, this leads to the cancellation of the pending search request. This is a very nice but hard-to-get-right property, which you get for free by combining switchMap() with cancellable Observables returned by Angular's Http service. This can save you a lot of headaches without worrying about properly handling all the race conditions that typically occur with async cancellation.
You are right.
As you can see, switchMap is used with search functionality. The searchbox in this example is programmed to basically emit a search request when the user enters text in the textbox (with a 350ms debounce or delay).
This means that when the user enters 'har', ngrx sends a search request to the service. When the user enters another letter 'r', the previous request is canceled (since we are not interested in 'har' anymore, but 'harr').
It is very nicely shown in the marble diagrams provided in another answer.
In mergeMap, the previous Observables are not canceled and therefore '30' and '50' are mixed together. Using switchMap, only the 5s are emitted, because the 3's are canceled.
mergeMap
Projects each source value to an Observable which is merged in the output Observable.
Maps each value to an Observable, then flattens all of these inner Observables using mergeAll.
switchMap
Projects each source value to an Observable which is merged in the output Observable, emitting values only from the most recently projected Observable.
Maps each value to an Observable, then flattens all of these inner Observables using switch.
Source: ES6 Observables in RxJS
You don't want an API save data request to cancel. That is why you would use mergeMap. A search query can be thrown away, no loss of data, and the user might be editing their query and are not interested in the data for the old one. Hence switchMap.
Yes, if you are no longer concerned with the response of the previous request when a new Input arrives switchMap is a suitable operator than mergeMap.
Is this true? I'm currently a little confused:
observable which is made(merge, scan, map, filter... ) based on another hot observable is definitely hot?
Whatif I merged hot and cold observable together? is the new observable hot or cold?
Edit: test, result not expected
var source = Rx.Observable.fromEvent(window, 'mousemove')
.merge(Rx.Observable.from([1,2,3,4,5]))
.scan(acc=>++acc, 0);
setTimeout(()=>{
source.subscribe(x=>{
console.log('sub1', x)
})
}, 4000)
setTimeout(()=>{
source.subscribe(x=>{
console.log('------sub2', x)
})
}, 6000)
As paulpdaniels mentioned in his answer, mousemove should be a hot observable, so it should emit values even if there's no subscription.
But in the code example above, I delayed the subscription for 4 and 6 seconds. So when the application starts, I move my mouse around. When it reaches the 4th second, it logs out:
"sub1" 1
"sub1" 2
"sub1" 3
"sub1" 4
"sub1" 5
why it starts from 1?? doesn't it should have been accumulating values for 4 seconds? it should start from a bigger number from my understanding.
When it reaches the 6th second, it logs out:
"------sub2" 1
"------sub2" 2
"------sub2" 3
"------sub2" 4
"------sub2" 5
Why? doesn't it mean mousemove is cold observable?? or the problem is on scan operator??
PS: I'm using rxjs 5
That is correct. Hot simply means that it can produce events in the absence of subscribers. So if a source is hot then regardless of what comes after it, that source will remain hot.
Consider a simple example of a mouse event handler.
var mousemove = Rx.Observable.fromEvent(window, 'mousemove');
No matter what operators you attach to it, the source (mouse event handler) will produce a new event for every mouse movement. If there are no listeners attached, then none of those events will get processed and none of the logic after mousemove does anything, but those events will still be dutifully created and then silently forgotten.
There are some exceptions where a hot Observable can be made to appear like a cold Observable say by using replay, but that is simply buffering events for future subscribers, not changing the nature of the source.
As to your question regarding merging hot and cold. Since one of the sources is hot then the combined source would also be considered hot, because again it would continue to produce events regardless of the presence of subscribers. i.e.
//Still hot because the mouse does not stop generating events in the
//absence of subscribers.
var source = Rx.Observable.fromEvent(window, 'mousemove')
.merge(Rx.Observable.from([1,2,3,4,5]));
This is what I have:
CitiesObservable = Observable
.FromAsync(apiClient.GetCitiesTask)
.Retry();
apiClient.GetCitiesTask returns a task of type: Task<List<City>>
The problem is that every time I add a subscriber to the observable, apiClient.GetCitiesTask gets called again. How can I cache the result once it has completed successfully?
Thanks
Question reworded
I want apiClient.GetCitiesTask to be called as many times as needed (until it doesn't fail), but once it success, all late subscribers should use a cached result.
Conclusion
2 solutions arose, one I found and the other (the selected answer).
Solution A: (actually is almost a solution)
CitiesObservable = Observable.FromAsync(apiClient.GetCitiesTask).Publish();
CitiesObservable.Connect();
// Then you can subscribe as you want. But! you won't receive the cached value on late subscribers, only the onCompleted signal.
Solution B: (by #Bluesman)
CitiesObservable = Observable.StartAsync(
() => Observable.FromAsync(apiClient.GetPlacesTask<City>).Retry().ToTask()
);
// Then you can subscribe as you want.
What about....
Observable
.StartAsync(() => Observable
.FromAsync(reserbusAPI.GetPlacesTask<City>)
.Retry()
.ToTask());
The outer StartAsync makes sure the eventual result from the created task is buffered while the inner FromAsync with Retry makes sure that GetPlacesTask is called as many times as needed. However, the whole retrying-thing still starts even before the first subscription.