I have a observable that emits data which looks something like this:
struct AlertData {
let name: String
let actionStream: PublishSubject<Void>
}
So when I receive AlertData from observable I directly bind it to another observer, which works fine. But before binding it to another observer, I wanted to get the 'actionStream' and get events from it.
So, this is how I emitting the AlertData:
let alertStream = PublishSubject<AlertData>()
alertStream.onNext(***)
This is the receiving part:
alertStream.bind(to: anotherObserver).disposed(by: disposeBag)
But before binding alertStream to anotherObserver, I wanted the actionStream which is inside the AlertData and receive any events emitted from it. What is the proper way of doing it?
let actionStream = alertStream
.flatMap { $0.actionStream.asObservable() }
You might not actually want flatMap (which acts as a flat map merge) specifically. Look into the variations on flatMap and see which one is most suitable for your particular situation: RxSwift’s Many Faces of FlatMap
Related
I'm currently using RIBs and ReactorKit to bind networking data.
The problem here is that the network results come out as Observables, which I have a hard time binding to ReactorKit.
Please let me know if there is a way to strip the Observable or turn it into a value.
Just like when BehaviorRelay is .value, the value comes out...
dependency.loadData.getData().flatMap { $0.detailData.flatMap { $0.result }}
====>> Obervable
now what do i do? TT
Please let me know if there is a way to strip the Observable or turn it into a value.
This is called "leaving" or "breaking" the monad and is a code smell.
In production code, it is rarely advised to 'break the monad', especially moving from an observable sequence to blocking methods. Switching between asynchronous and synchronous paradigms should be done with caution, as this is a common root cause for concurrency problems such as deadlock and scalability issues.
-- Intro to Rx
If you absolutely have to do it, then here is a way:
class MyClass {
private (set) var value: Int = 0
private let disposeBag = DisposeBag()
init(observable: Observable<Int>) {
observable
.subscribe(onNext: { [weak self] new in
self?.value = new
}
.disposed(by: disposeBag)
}
}
With the above, when you query value it will have the last value emitted from the observable. You risk race conditions doing this and that's up to you to deal with.
That's the direct answer to your question but it isn't the whole story. In ReactorKit, the API call should be made in your reactor's mutate() function. That function returns an Observable<Mutation> so instead of breaking the monad, you should be just mapping the API response into a Mutation which is likely a specific enum case that is then passed into your reduce() function.
Background
I'm trying to observe one Int stream (actually I'm not, but to make the argument easier) and do something with it while combining that stream to multiple other streams, say a String stream and a Double stream like the following:
// RxSwift
let intStream = BehaviorSubject<Int>(value: 0) // subscribe to this later on
let sharedStream = intStream.share()
let mappedStream = sharedStream.map { ... }.share()
let combinedStream1 = Observable.combineLatest(sharedStream, stringStream).map { ... }
let combinedStream2 = Observable.combineLatest(sharedStream, doubleStream).map { ... }
The above code is just to demonstrate what I'm trying to do. The code above is part of view model code (the VM part of MVVM), and only the first map (for mappedStream) runs, while the others are not called.
Question
What is wrong with the above approach, and how do I achieve what I'm trying to do?
Also, is there a better way to achieve the same effect?
Updates
I confirmed that setting the replay count to 1 makes things work. But why?
The code above all goes in the initialization phase of the view model, and the subscription happens afterwards.
Okay, I have an answer but it's a bit complex... One problem is that you are using a Subject in the view model, but I'll ignore that for now. The real problem comes from the fact that you are using hot observables inappropriately (share() make a stream hot) and so events are getting dropped.
It might help if you put a bunch of .debug()s on this code so you can follow along. But here's the essence...
When you subscribe to mappedStream, it subscribes to the share which in turn subscribes to the sharedStream, which subscribes to the intStream. The intStream then emits the 0, and that 0 goes down the chain and shows up in the observer.
Then you subscribe to the combinedStream1, which subscribes to the sharedStream's share(). Since this share has already been subscribed to, the subscriptions stop there, and since the share has already output it's next event, the combinedStream1 doesn't get the .next(0) event.
Same for the combinedStream2.
Get rid of all the share()s and everything will work:
let intStream = BehaviorSubject<Int>(value: 0) // subscribe to this later on
let mappedStream = intStream.map { $0 }
let combinedStream1 = Observable.combineLatest(intStream, stringStream).map { $0 }
let combinedStream2 = Observable.combineLatest(intStream, doubleStream).map { $0 }
This way, each subscriber of intStream gets the 0 value.
The only time you want to share is if you need to share side effects. There aren’t any side effects in this code, so there’s no need to share.
I'm new in RxSwift, I don't understand what is difference between do(onNext:) and subscribe(onNext:).
I google it but did't found good resources to explain the difference.
At the beginning of a cold Observable chain there is a function that generates events, for e.g. the function that initiates a network request.
That generator function will not be called unless the Observable is subscribed to (and by default, it will be called each time the observable is subscribed to.) So if you add a do(onNext:) to your observable chain, the function will not be called and the action that generates events will not be initiated. You have to add a subscribe(onNext:) for that to happen.
(The actual internals are a bit more complex than the above description, but close enough for this explanation.)
The do operator allows you to insert side effects; that is, handlers to do things that will not change the emitted event in any way. do will just pass the event through to the next operator in the chain.
The method for using the do operator is here.
And you can provide handlers for any or all of these events.
Let's say We have an observable that never emits anything. Even though it emits nothing, it is still an observable and we can subscribe to it. do operator allows us to do something when a subscription was made to it.
So below example will print "Subscribed" when a subscription was made to that observable.
Feel free to include any of the other handlers if you’d like; they work just like subscribe’s handlers do
let observable = Observable<Any>.never()
let disposeBag = DisposeBag()
observable
.do(onSubscribe: {
print("Subscribed")
})
.subscribe(
onNext: { element in
print(element)
},
onCompleted: {
print("Completed")
},
onDisposed: {
print("Disposed")
}
)
.disposed(by: disposeBag)
I'm working on something that is recording data coming from a queue. It was easy enough to process the queue into an Observable so that I can have multiple endpoints in my code receiving the information in the queue.
Furthermore, I can be sure that the information arrives in order. That bit works nicely as well since the Observables ensure that. But, one tricky bit is that I don't want the Observer to be notified of the next thing until it has completed processing the previous thing. But the processing done by the Observer is asynchronous.
As a more concrete example that is probably simple enough to follow. Imagine my queue contains URLs. I'm exposing those as an Observable in my code. The I subscribe an Observer whose job is to fetch the URLs and write the content to disk (this is a contrived example, so don't take issue with these specifics). The important point is that fetching and saving are async. My problem is that I don't want the observer to be given the "next" URL from the Observable until they have completed the previous processing.
But the call to next on the Observer interface returns void. So there is no way for the Observer to communicate back to me that has actually completed the async task.
Any suggestions? I suspect there is probably some kind of operator that could be coded up that would basically withhold future values (queue them up in memory?) until it somehow knew the Observer was ready for it. But I was hoping something like that already existed following some established pattern.
similar use case i ran into before
window.document.onkeydown=(e)=>{
return false
}
let count=0;
let asyncTask=(name,time)=>{
time=time || 2000
return Rx.Observable.create(function(obs) {
setTimeout(function() {
count++
obs.next('task:'+name+count);
console.log('Task:',count ,' ', time, 'task complete')
obs.complete();
}, time);
});
}
let subject=new Rx.Subject()
let queueExec$=new Rx.Subject()
Rx.Observable.fromEvent(btnA, 'click').subscribe(()=>{
queueExec$.next(asyncTask('A',4000))
})
Rx.Observable.fromEvent(btnB, 'click').subscribe(()=>{
queueExec$.next(asyncTask('B',4000))
})
Rx.Observable.fromEvent(btnC, 'click').subscribe(()=>{
queueExec$.next(asyncTask('C',4000))
})
queueExec$.concatMap(value=>value)
.subscribe(function(data) {
console.log('onNext', data);
},
function(error) {
console.log('onError', error);
},function(){
console.log('completed')
});
What you describe sounds like "backpressure". You can read about it in RxJS 4 documentation https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/backpressure.md. However this is mentioning operators that don't exist in RxJS 5. For example have a look at "Controlled Observables" that should refer to what you need.
I think you could achieve the same with concatMap and an instance of Subject:
const asyncOperationEnd = new Subject();
source.concatMap(val => asyncOperationEnd
.mapTo(void 0)
.startWith(val)
.take(2) // that's `val` and the `void 0` that ends this inner Observable
)
.filter(Boolean) // Always ignore `void 0`
.subscribe(val => {
// do some async operation...
// call `asyncOperationEnd.next()` and let `concatMap` process another value
});
Fro your description it actually seems like the "observer" you're mentioning works like Subject so it would make maybe more sense to make a custom Subject class that you could use in any Observable chain.
Isn't this just concatMap?
// Requests are coming in a stream, with small intervals or without any.
const requests=Rx.Observable.of(2,1,16,8,16)
.concatMap(v=>Rx.Observable.timer(1000).mapTo(v));
// Fetch, it takes some time.
function fetch(query){
return Rx.Observable.timer(100*query)
.mapTo('!'+query).startWith('?'+query);
}
requests.concatMap(q=>fetch(q));
https://rxviz.com/v/Mog1rmGJ
If you want to allow multiple fetches simultaneously, use mergeMap with concurrency parameter.
is to possible to attach custom events to the document's body? I wanna implement a simple message bus. I did it many times with jQuery, but not sure how to do it in ExtJS.
Thank you.
You don't need to attach the event to the body or any DOM element, I would rather attach them to a dedicated object.
Like :
MyApp.MessageBus = Ext.extend(Ext.util.Observable, {
// I don't think it's necessary to declare all events
events : {
sayHello : true
}
});
MsgBus = new MyApp.MessageBus();
And, somewhere in your code :
MsgBus.on('sayHello', function(who) { alert("Hello, " + who); });
And, on another place :
MsgBus.fireEvent('sayHello', 'innerJL');
I agree with Drasill, and there are many examples in the Ext community for creating simple bus implementations based on Observable. See here and here for starters.
Putting it on the document itself is problematic in terms of firing custom events. It can be done -- you would just need to make a class which extended Ext.util.Observable as your representation of the result of Ext.getDoc.
But you can bubble up events by adding to the Observables you already have enableBubble : ['foo', 'bar']. That means you do fireEvent('foo') on any child Observable and it'll bubble up to the top where your listener is.