var button = document.querySelector('button');
var obs1 = Rx.Observable.fromEvent(button, 'click').scan(count => count + 1, 0).multicast(new Rx.Subject())
function obs2$(result) {
return Rx.Observable.of(result).delay((Math.random() * 10000 % 2000) + 1000)
}
obs1.concatMap((count) => {
return obs2$(count)
}).subscribe(x => console.log("Sync Finished " + x))
obs1.subscribe(x => console.log("Sync Request " + x))
obs1.connect()
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://unpkg.com/#reactivex/rxjs#5.3.0/dist/global/Rx.js"></script>
<button>Click me</button>
<h3 id='show'>Foo</h3>
I am implementing a syncing system with two observables in play. One is just a stream of sync request actions:
actions$.ofType('SYNC_REQUESTED')
actions$ in this case is a redux-observable construct representing Redux actions.
The other observable performs the actual sync and dispatches a Redux action when it is completed.
I want it to behave such that if a sync is currently being performed, and another emission is made to the sync request observable, the sync gets queued up (like in the case of concatMap). However, if multiple emissions are made during a sync, only one of them is actually taken.
In other words, I want a switchMap that doesn't cancel the current sync.
If you run the attached snippet and click the button five times in a row within a second you'll see:
Sync Request 1
Sync Request 2
Sync Request 3
Sync Request 4
Sync Request 5
Sync Finished 5
If you use switchMap.
And using concatMap you'll see:
Sync Request 1
Sync Request 2
Sync Request 3
Sync Request 4
Sync Request 5
Sync Finished 1
Sync Finished 2
Sync Finished 3
Sync Finished 4
Sync Finished 5
I want it read:
Sync Request 1
Sync Request 2
Sync Request 3
Sync Request 4
Sync Request 5
Sync Finished 1
Sync Finished 5
Now because I am using Redux I could always use the store to help me out, maybe keeping track of syncPending and syncInProgress flags and using skipWhile to skip sync requests that happen while a sync is in progress if there's already one pending.
I was wondering if there was a way to do this just by using RxJS though since that would require extra code / actions / reducers / Redux state.
try take(1) and repeat()
var button = document.querySelector('button');
var obs1 = Rx.Observable.fromEvent(button, 'click').scan(count => count + 1, 0).multicast(new Rx.Subject())
function obs2$(result) {
return Rx.Observable.of(result).delay(2000)
}
Rx.Observable.merge(obs1.audit(()=>obs1.mergeMap(obs2$)),
obs1.take(1).mergeMap(obs2$).repeat())
.subscribe(console.log)
obs1.subscribe(x => console.log("Sync Request " + x))
obs1.connect()
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://unpkg.com/#reactivex/rxjs#5.3.0/dist/global/Rx.js"></script>
<button>Click me</button>
<h3 id='show'>Foo</h3>
Related
I am experimenting Dart, and I can't explain two observations.
I wonder why the "onDone" handler assigned to a stream subscription does not fire up.
I wonder why the "onPause" and "onResume" handlers fire up only once.
The code:
import 'dart:async';
import 'dart:io';
/// This class encapsulates all the necessary data used by the "onValue" event
/// handler (the construct avoids using global variables).
class OnValueHandlerContainer {
static StreamSubscription<int> _streamSubscriber;
static setStreamSubscriber(StreamSubscription<int> stream) {
_streamSubscriber = stream;
}
// This static method is the handler executed when a event is received through
// the stream.
//
// WARNING: you have absolutely no idea when this handler will be executed.
// Do not assume that it will be executed right after the execution of the code
// that emits an event. It may be executed several lines (of codes) below the
// line that emits the event. It may well be executed after the end of the
// script.
static void onValue(int value) {
// At this point: the state of the subscription is (inevitably) "active".
print("onValue: An event has been raised. The associated value is ${value}!");
print(" Pause the subscription. Wait for 1 second. Resume the subscription");
// Note 1: once a Dart function starts executing, it continues executing until
// it exits. When managing interrupts in C, it is necessary to protect
// interrupt handlers from being interrupted. This is not the case in
// Dart : a function (and, thus, an event handler) cannot be interrupted
// by the occurrence of another event.
// => The code below has no sense, other than experimentation.
// Note 2: while paused, the subscription will not fire any events. If it receives
// events from its source, they will be buffered until the subscription
// is resumed.
_streamSubscriber.pause();
sleep(Duration(seconds: 1));
_streamSubscriber.resume();
// At this point: the state of the subscription is "active".
}
}
main() async {
// Create a controller.
// A StreamController gives you a new stream and a way to add events to the stream
// at any point, and from anywhere. The stream has all the logic necessary to handle
// listeners and pausing. You return the stream and keep the controller to yourself.
StreamController<int> sc = StreamController<int>(
onListen: () => print("Controller: the stream has been assigned a listener!"),
onCancel: () => print("Controller: the stream has been canceled!"),
// As you may notice, the event handlers are not executed every time the
// subscription gets paused or resumed.
//
// This behaviour comes from these facts:
// - Dart is single-threaded.
// - An event handler cannot be interrupted: once a Dart function starts
// executing, it continues executing until it exits. In other words, Dart
// functions can’t be interrupted by other Dart code.
// See https://webdev.dartlang.org/articles/performance/event-loop
// - A stream is a FIFO.
onPause: () => print("Controller: the stream has been paused!"),
onResume: () => print("Controller: the stream has been resumed!")
);
// Get the stream created by the stream controller.
// Right now, this stream has no assigned listener.
Stream<int> stream = sc.stream;
print("Does the stream provided by the controller have a listener ? ${sc.hasListener ? 'yes' : 'no'} - the answer should be no.");
// Push values into the stream controlled by the stream controller.
// Because no listener subscribed to the stream, these values are just stored
// into the stream.
for(int i=0; i<3; i++) {
print("Send the value ${i} into the stream.");
sc.add(i);
}
// Add a listener to the stream.
// Now the stream has an assigned listener.
StreamSubscription<int> subscriber = stream.listen(OnValueHandlerContainer.onValue);
OnValueHandlerContainer.setStreamSubscriber(subscriber);
subscriber.onDone(() => print("The subscription is done!"));
print("Does the stream provided by the controller have a listener ? ${sc.hasListener ? 'yes' : 'no'} - the answer should be yes.");
// Wait for 10 seconds.
print("Start waiting for 10 seconds");
Future.delayed(Duration(seconds: 10)).then((var v) => print("10 seconds ellapsed!"));
print("End of script");
}
The result:
Does the stream provided by the controller have a listener ? no - the answer should be no.
Send the value 0 into the stream.
Send the value 1 into the stream.
Send the value 2 into the stream.
Controller: the stream has been assigned a listener!
Does the stream provided by the controller have a listener ? yes - the answer should be yes.
Start waiting for 10 seconds
End of script
onValue: An event has been raised. The associated value is 0!
Pause the subscription. Wait for 1 second. Resume the subscription
Controller: the stream has been paused!
onValue: An event has been raised. The associated value is 1!
Pause the subscription. Wait for 1 second. Resume the subscription
onValue: An event has been raised. The associated value is 2!
Pause the subscription. Wait for 1 second. Resume the subscription
Controller: the stream has been resumed!
10 seconds ellapsed!
Basically, the code provided performs the following actions :
A stream controller is created.
3 events are injected into the stream provided by the controller.
A listener subscribes to the stream provided by the controller.
We assign an "onDone" handler to the listener subscription.
Within the stream listener (OnValueHandlerContainer::onValue) we pause and resume the subscription.
The stream listener fires up 3 times, as expected.
However:
the "onDone" handler is never executed. I expect it to be executed at the end of the script execution, while the controller is being destroyed (and, thus, the subscription gets closed).
the "onPause" and "onResume" handlers fire up only once. I expect them to be executed 3 times.
Any idea ?
The reason you don't get a "done" event is that you never close the stream subscription.
The reason you don't get more "pause" events is that the stream subscription is clever.
The first thing you do is to add a lot of events, before anyone even listens to the stream. You should never do that in real code, instead only start adding events when the onListen is called, and stop again when onPause is called, until the subscription is resumed.
Here, the stream subscription gets stuffed up with a number of events, then it delivers one event, and then the subscription is paused.
The subscription dutifully reports that back to the controller.
Then the subscription gets a resume. This is where it gets clever. Since it already has events to deliver, it does not report the resume back to the controller. It doesn't actually want more events right now, there are plenty to deliver. And so it delivers the buffered events, one at a time, until the buffer is empty. At that point, it reports the resume back to the controller.
The controller reports that work has been resumed, but since nobody adds any more events, and nobody calls close, nothing further will happen.
I want to ensure that a given API call is throttled so that for a given time interval, only a single request is fired, and that the other, throttled requests wait and receive the results of the request that was actively fired
Example
const generateReport = (args) => client.get(...)
const generateReportA = (argsForA) =>
generateReport(argsForA).then(saveReportSomewhere)
const generateReportB = (argsForB) =>
generateReport(argsForB).then(saveReportSomewhere)
const generateReportC = (argsForC) =>
generateReport(argsForC).then(saveReportSomewhere)
If we then run the statements below
generateReportA(...).then(console.log) // should log result of C
generateReportB(...).then(console.log) // should log result of C
generateReportC(...).then(console.log) // should log result
right after each other, I only want to fire the request associated with generateReportC and I'd like both generateReportA and generateReportB to receive and handle the result of generateReportC.
In the end generateReport should have been called once and saveReportSomewhere should have been called 3 times, each with the result from generateReportC
Is this possible?
This will fire C request get result and save it, then trigger A and B at the same time with result from C and save results immediately after every emit.
generateReportC.pipe(
tap(cResult => saveReportSomewhere(cResult)),
mergeMap(cResult => merge(generateReportA(cResult), generateReportB(cResult))),
tap(result => saveReportSomewhere(result))
).subscribe();
I'd like to implement websocket reconnect in webapp if internet connection is lost. In order to detect that internet is lost I use ping-pong approach, which means that I send from client ping-message and server returns me pong-message.
When webapp loaded I send init ping message and start to listen a reply on socket some kind of this:
this.websocket.onmessage = (evt) => {
try {
const websocketPayload: any = JSON.parse(evt.data);
if (websocketPayload.pong !== undefined && websocketPayload.pong == 1) {
this.pingPong$.next('pong');
}
It means that internet connection looks ok and we can continue. Also I have the follow code:
Observable.race(
Observable.of('timeout').delay(5000).repeat(),
this.pingPong$
).subscribe((data) => {
console.log("[ping-pong]:", data);
if (data == 'pong') {
Observable.interval(5000).take(1).subscribe(() => {
console.log("[ping-pong]:sending ping")
this.send({ping:1})
});
} else if (data == 'timeout'){
// show reconnect screen and start reconnect
console.error("It looks like websocket connection lost");
}
});
But!
When this.pingPong$ subject stops to emit events - .next() doesn't happen because of we can't get response when I break connection manually - I considered that in Observable.race this observable will be emitted
Observable.of('timeout').delay(5000).repeat()
But my subscribe never happens if this.pingPong$ stop emitting.
Why ?
Thank you
race picks and keeps subscribed to the first Observable that emits.
So if your this.pingPong$ starts emitting and then stops it makes no difference because race keeps subscribed to this.pingPong$. The other Observables don't matter any more. You might want emit one value from this.pingPong$ and the repeat the whole process. For example like the following:
Observable.race(
Observable.of('timeout').delay(5000).repeat(),
this.pingPong$
)
.pipe(
take(1), // complete the chain immediately
repeat() // resubscribe after take(1) completes the chain
)
.subscribe(...);
Obviously it mostly depends on what you want to do but I hope you get the point.
I'm working on a use case that requires that if an observable has not emitted a value within a certain amount of time then we should do some side effect.
To give a practical use case:
open web socket connection
if no message has been sent/received within X time then close web socket connection and notify user
This requires for a timer to be initiated on every emitted value and upon initial subscription of observable which will then run some function after the allotted time or until a value is emitted in which the timer resets. I'm struggling to do this the Rx way. Any help would be appreciated :)
debounceTime is the operator you're looking for: it only emits a value if no others follow within a specific timeout. Listening for the first message of the debounced stream will let you time out and clean up your websocket connection. If you need to time out starting from the opening of the stream, you can simply startWith. Concretely:
messages$.startWith(null)
.debounceTime(timeout)
.take(1)
.subscribe(() => { /* side effects */ });
Edit: if instead you're looking to end the a message stream entirely when it times out (e.g. you clean up in the onComplete handler), just cram debounceTime into a takeUntil:
messages$.takeUntil(
messages$.startWith(null)
.debounceTime(timeout)
).subscribe(timeout_observer);
With a timeout_observable: Observer<TMessage> that contains your cleanup onComplete.
You can do this with race:
timer(5000).race(someSource$)
.subscribe(notifyUser);
If someSource$ notifies faster than timer(5000) (5 seconds), then someSource$ "wins" and lives on.
If you only want one value from someSource$, you can obviously have a take(1) or first() on someSource$ and that will solve that issue.
I hope that helps.
Might not be the perfect answer but it does what you asked, it depends on how you want to disconnect, there might be some variation to be done
const source = new Rx.Subject();
const duration = 2000;
source.switchMap(value=>{
return Rx.Observable.of(value).combineLatest(Rx.Observable.timer(2000).mapTo('disconnect').startWith('connected'))
}).flatMap(([emit,timer])=>{
if(timer=='disconnect'){
console.log('go disconnect')
return Rx.Observable.throw('disconnected')
}
return Rx.Observable.of(emit)
})
//.catch(e=>Rx.Observable.of('disconnect catch'))
.subscribe(value=>console.log('subscribed->',value),console.log)
setTimeout(() => source.next('normal'), 300);
setTimeout(() => source.next('normal'), 300);
setTimeout(() => source.next('last'), 1800);
setTimeout(() => source.next('ignored'), 4000);
<script src="https://unpkg.com/rxjs#5/bundles/Rx.min.js"></script>
A timer is initiated on each element and if it takes 4 seconds to be shown, then it will timeout and you can execute your function in the catchError
Here an example, it displays aa at T0s, then bb at t3s, then timeout after 4 second because the last one cc takes 10s to be displayed
import './style.css';
screenLog.init()
import { from } from 'rxjs/observable/from';
import { of } from 'rxjs/observable/of';
import { race } from 'rxjs/observable/race';
import { timer } from 'rxjs/observable/timer';
import { groupBy, mergeMap, toArray, map, reduce, concatMap, delay, concat, timeout, catchError, take } from 'rxjs/operators';
// simulate a element that appear at t0, then at t30s, then at t10s
const obs1$ = of('aa ');
const obs2$ = of('bb ').pipe(delay(3000));
const obs3$ = of('cc ').pipe(delay(10000));
const example2 = obs1$.pipe(concat(obs2$.pipe(concat(obs3$))), timeout(4000), catchError(a => of('timeout'))); // here in the catchError, execute your function
const subscribe = example2.subscribe(val => console.log(val + ' ' + new Date().toLocaleTimeString()));
I've been struggling to come up with an Rx strategy for a particular situation. I'm hoping someone could point me in the right direction.
Basically, I have a socket feed that I would like to skip based on a boolean value. When the stream is skipping the socket, I need to keep a running buffer of the latest value sent from socket.
Once I am no longer skipping the socket events, then push down the stream the last value that was emitted when it was skipping, but only under another condition (bool), and re-start listen to socket events
So basically:
Listen to socket feed
takeWhile(bool)
When start listening again, apply last values while skipping
socket, if reapply==true
Didn't get far, but this is what I have:
Rx.Observable.interval(1000)
.skipWhile(()=>isSkipping)
.bufferWhileSkipping??
.applySkippedValuesAfterSkipping(ifisReapply)??
.subscribe(val=>console.log(val));
Perhaps skipWhile is not the right approach but was the only one that kind of made sense…
You could do it similarly to the following (I'm assuming your isSkipping can be an Observable):
const isSkipping = new BehaviorSubject(false);
Observable.interval(100)
.take(20)
.window(isSkipping)
.withLatestFrom(isSkipping)
.switchMap(([observable, skipping]) => skipping
? observable.takeLast(1).map(val => 'last:' + val)
: observable)
.subscribe(console.log);
setTimeout(() => isSkipping.next(true), 500);
setTimeout(() => isSkipping.next(false), 1050);
setTimeout(() => isSkipping.next(true), 1500);
setTimeout(() => isSkipping.next(false), 1850);
Every time isSkipping emits a value the window operator creates a new Observable that just re-emits everything or chains the .takeLast(1) operator when skipping is set to true.
The example above prints the following output to the console:
0
1
2
3
last:9
10
11
12
13
last:16
17
18
19