Does switchmap automatically calls the subscription? - rxjs

I am trying to obtain a behavior such that if current observable is changed in value, the respective observables linked should be invoked respectively.
The following code works as intended, but I just need some conformation regarding this behavior, I am trying to implement this on production site.
I know switchMap unsubscribes from current subscription and resubscribes to new one that's returned.
But, Does it also call the new observable subscribed to kick in and run the code it has or is this run by the pipe operator or am I missing any crucial concept ?
Can some one kindly, clarify this.
Thank you.
Here is the stackblitz code link ::: https://stackblitz.com/edit/rxjs-ktnf9x :: and Overview
let a = new BehaviorSubject(null);
let b = new BehaviorSubject(null);
let c = new BehaviorSubject(null);
let d = new BehaviorSubject(null);
let a$ = a.asObservable();
let b$ = b.asObservable();
let c$ = c.asObservable();
let d$ = d.asObservable();
d$
.pipe(
switchMap(
data => {
console.log("from d :: " + data);
return c$;
}
)
)
.pipe(
switchMap(
data => {
console.log("from c :: " + data);
return b$;
}
)
)
.pipe(
switchMap(
data => {
console.log("from b :: " + data);
return a$;
}
)
)
.subscribe(
data => {
console.log("from a :: " + data);
console.log(""); // for next line.
}
)
b.next("calls");
a.next("allSubs");
c.next("it");
d.next("does");
d.next('yes');
finally --> Outputs ::: yes it calls allSubs

To make it simpler to explain. SwitchMap unsubscribe c$ when d$ emits. if d emit once and c keeps emitting you will get a sequence of d-c-c-c
if d emits again in the middle you will get d-c-c-c-d-c-c-c
d$
.pipe(
switchMap(
data => {
console.log("from d :: " + data);
return c$;
}
)
)

Related

How to cancel the execution or unsubscribe previous inner observable returned from switchmap

async function asyncFunction(source) {
console.log(source + ' started');
for (let i = 0; i < 4; ++i) {
console.log('"' + source + '"' + ' number ' + i);
await new Promise((r) => setTimeout(r, 1000));
}
console.log(source + ' completed');
return `asyncFuntion ${source} returns completed`;
}
const epic = interval(2000).pipe(switchMap(value => {
console.log("source value " + value);
return from(asyncFunction(value))
}));
epic.subscribe(
console.log,
console.error,
() => console.log('completed epic')
);
Above is my code and each time a new value gets emitted from the interval, I want the previous execution of asynFunction to stop running but switchMap does not do it. I am manually calling the subscribe method here, but in rxjs framework, I don't have to call the subscribe method since the framework is doing it for me somewhere. I have tried so many things (takeUtil, take and etc) and still unable to find the solution. All I want is for the previous execution/call to the asynFunction, which runs longer than the time it takes to get a new emitted value from the interval, to be terminated when a new source value is emitted.

Trying to understand this retryWhen/scan/delay rxjs sequence

So I came up with this implementation of a delayed retry for HTTP requests, this works fine and I have a good understanding of it as well.
retryWhen(err => {
let retryCount = 0;
let nextDelay = 0;
backoff = backoff < 0 || backoff === null ? DEFAULT_BACKOFF : backoff;
maxDelay = maxDelay < 0 || maxDelay === null ? DEFAULT_MAX_DELAY : maxDelay;
return err.pipe(
scan(idx => {
if (idx > maxRetries - 1) {
throw err;
} else {
idx++;
retryCount = idx;
nextDelay = Math.min(retryDelay + ((retryCount - 1) * backoff), maxDelay);
return idx;
}
}, 0),
tap(v => console.log(`Waiting ${nextDelay} ms for Retry #${retryCount}...`)),
delay(nextDelay),
tap(v => console.log(`Initiating HTTP Retry for context ${context}`))
)
First issue: The above code works fine when I use a constant value in the delay operator like so delay(3000) but it does not when I use the nextDelay variable. When I use the nextDelay variable there is no delay, it's like the variable is zero.
However the value is output correctly in the tap operator above it so I know it's in scope and the let is within scope of the retryWhen so should be good. I can't figure out why the delay does not work unless I use a value literal.
Second issue: I want to optimize the above code so that I don't use the variables retryCount and nextDelay, I want to compute those on the fly within the delay operator, however this operator takes only the amount of the delay as the input and does not have a reference to the idx emmitted by the scan above. I would like to do something like this:
scan(idx => ... code to either throw the error or emit the index ...),
delay(idx => Math.min(retryDelay + (idx - 1) * backoff), maxDelay)
The scan maps the original err element to the index idx but how exactly do I get that in the delay operator?
First issue: The above code works fine when I use a constant value in the delay operator like so delay(3000) but it does not when I use the nextDelay variable. When I use the nextDelay variable there is no delay, it's like the variable is zero.
The cause of the issue has been already pointed out in fridoo's answer.
However the value is output correctly in the tap operator
This is a hint of you could fix it. The difference is that delay(value) captures the value and in the case of tap(() => ...), the value will be evaluated every time its callback function will be invoked(in this case, on each Next notification).
If we take a look at delay's implementation
export function delay<T>(due: number | Date, scheduler: SchedulerLike = asyncScheduler): MonoTypeOperatorFunction<T> {
const duration = timer(due, scheduler);
return delayWhen(() => duration);
}
we'll see that it first captures the value in a timer observable, which will be then used with a delayWhen.
So, this first issue could be fixed with:
/* ... */
tap(v => console.log(`Waiting ${nextDelay} ms for Retry #${retryCount}...`)),
delayWhen(() => timer(nextDelay)),
tap(v => console.log(`Initiating HTTP Retry for context ${context}`))
/* ... */
Second issue: I want to optimize the above code so that I don't use the variables retryCount and nextDelay
We can use other RxJS operators for this:
retryWhen(err => {
backoff = backoff < 0 || backoff === null ? DEFAULT_BACKOFF : backoff;
maxDelay = maxDelay < 0 || maxDelay === null ? DEFAULT_MAX_DELAY : maxDelay;
return err.pipe(
// replacing `retryCount` with `map`'s index argument
map((err, idx) => {
// you can also throw the error here
if (idx >= maxRetries) { throw err; }
const retryCount = idx + 1;
// returning the `nextDelay`
return Math.min(retryDelay + ((retryCount - 1) * backoff), maxDelay);
})
delayWhen(nextDelay => timer(nextDelay)),
)
The function passed to retryWhen is only called once, on the first error. So delay(nextDelay) is called once when the err.pipe(...) observable is created (not when err emits). At that time nextDelay is still 0. You should put all your logic in an operator that returns a different value depending on values emitted by err.
To return an observable that emits with a changing delay you can mergeMap to a timer. This should get you started:
interface RetryStrategyConfig {
maxRetryAttempts?: number;
scalingDuration?: number;
maxDelay?: number;
excludedStatusCodes?: number[];
}
function genericRetryStrategy({
maxRetryAttempts = 6,
scalingDuration = 1000,
maxDelay = 5000,
excludedStatusCodes = []
}: RetryStrategyConfig = {}) {
return (attempts: Observable<any>) => {
return attempts.pipe(
mergeMap((error, i) => {
const retryAttempt = i + 1;
// if maximum number of retries have been met
// or response is a status code we don't wish to retry, throw error
if (
retryAttempt > maxRetryAttempts ||
excludedStatusCodes.find(e => e === error.status)
) {
return throwError(error);
}
const nextDelay = Math.min(retryAttempt * scalingDuration, maxDelay);
console.log(`Attempt ${retryAttempt}: retrying in ${nextDelay}ms`);
return timer(nextDelay);
}),
finalize(() => console.log("We are done!"))
);
};
}
obs$.pipe(
retryWhen(genericRetryStrategy())
)

Is it possible to use a Subject in ZeroMQ?

I have a little project that implements NATS Queueing
This is the code:
import * as NATS from '../node_modules/nats' // for typescript
var nats = require('nats');
var config = require('./config');
var opt: NATS.ClientOpts = {'noRandomize': false, 'reconnect': true,
'maxReconnectAttempts': -1, 'servers':config.nat.servers, 'user':
config.nat.user , 'pass': config.nat.pass };
var nc: NATS.Client = nats.connect(opt);
nc.on('error', function(e) {
console.error('Error nats: ' + e);
});
nc.on('close', function() {
console.error('CLOSED nats');
});
nc.subscribe('serviceA', { 'queue': 'A' }, function (request, replyTo) {
console.debug('I exec serviceA via nats:');
j = JSON.parse(request);
console.debug(j);
let ok = {"res": "i am response for service A"}
nc.publish(replyTo, JSON.stringify(ok));
}
let cmd = '{"a": 5, "b": "i am sample"}';
nc.requestOne('serviceA', cmd, {}, 15000, function (response) {
if (response.code && response.code === nats.REQ_TIMEOUT) {
console.error('server timeout');
console.error(response.code);
} else {
console.log('A see this response: ' + response);
}
});
nc.subscribe('serviceB', { 'queue': 'B' }, function (request, replyTo) {
console.debug('I exec serviceB via nats:');
j = JSON.parse(request);
console.debug(j);
let ok = {"res": "i am response for service B"}
nc.publish(replyTo, JSON.stringify(ok));
}
let cmd = '{"c": 5, "d": "i am sample"}';
nc.requestOne('serviceB', cmd, {}, 15000, function (response) {
if (response.code && response.code === nats.REQ_TIMEOUT) {
console.error('server timeout');
console.error(response.code);
} else {
console.log('B see this response: ' + response);
}
});
As you can see, there are 2 services - serviceA on queue A and serviceB on queue B and 2 clients: the first calls service A and the second calls service B
NATS implements Subject ( 'serviceA' and 'serviceB' )
Now, I want try to convert the example using ØMQ I found a similar sample using ZeroMQ
But I can find nothing sample on Subject.
Perhaps ØMQ uses ROUTER so as to implements a subject
Can you help me to implement subject into a ZeroMQ example?
Q: Is it possible to use a Subject in ZeroMQ?A: Yes, it is:
The long story short - one does not need any zmq.ROUTER for doing this, just use a PUB / SUB formal pattern.
Beware: ZeroMQ Socket()-instance is not a tcp-socket-as-you-know-it.
Best
read about the main conceptual differences in [ ZeroMQ hierarchy in less than a five seconds ] Section.
The Publisher side:
import zmq
aCtx = zmq.Context()
aPub = aCtx.Socket( zmq.PUB )
aPub.bind( "tcp://123.456.789.012:3456" )
aPub.setsockopt( zmq.LINGER, 0 )
aPub.setsockopt( zmq.<whatever needed to fine-tune the instance>, <val> )
i = 0
while true:
try:
aPub.send( "ServiceA::[#{0:_>12d}] a Hello World Message.".format( i ) )
aPub.send( "ServiceABCDEFGHIJKLMNOPQRSTUVWXYZ........" )
aPub.send( "ServiceB::[#{0:_>12d}] another message...".format( i / 2 ) ) if ( i == ( 2 * ( i / 2 ) ) ) else pass
sleep( 1 ); i += 1
except KeyboardInterrupt:
print( "---< will exit >---" )
break
print( "---< will terminate ZeroMQ resources >---" )
aPub.close()
aCtx.term()
The Subscriber side:
import zmq
aCtx = zmq.Context()
aSub = aCtx.Socket( zmq.SUB )
aSub.connect( "tcp://123.456.789.012:3456" )
aSub.setsockopt( zmq.LINGER, 0 )
aSub.setsockopt( zmq.SUBSCRIBE, "ServiceA" ) # Subject ( 'serviceA' and 'serviceB' )
aSub.setsockopt( zmq.SUBSCRIBE, "ServiceB" ) # Kindly see the comments below
# # Kindly see API on subscription management details
#
# # Yet another "dimension"
# # to join ingress
# # from multiple sources
#Sub.connect( "<transport-class>://<addr>:<port>" )
# <transport-class :: { inproc | ipc | tcp | pgm | epgm | vmci }
# .connect()-s the same local SUB-AccessPoint to another PUB-side-AccessPoint
# to allow the PUB/SUB Scalable Formal Communication Archetype Pattern
# join a message flow from different source PUB-sides
#Sub.setsockopt( zmq.SUBSCRIBE, "ServiceZ" )
while true:
try:
print( "<<< (((_{0:s}_)))".format( aSub.recv() ) )
except KeyboardInterrupt:
print( "---< will exit >---" )
break
print( "---< will terminate ZeroMQ resources >---" )
aSub.close()
aCtx.term()

RxJS: How to emit original values, then reduce upon completion?

I would like to emit all original values from an RxJS stream, and then emit a summary upon completion.
Reduce stops the original values from emitting. Scan emits each total rather than the original values.
Here is my hacky solution:
let total = {
total: 0
};
Rx.Observable.range(1, 3)
.do(val => {
total.total += val;
})
.concat(Rx.Observable.of(total))
.subscribe(
value => {
console.log('Next:', value)
}
);
// Next: 1
// Next: 2
// Next: 3
// Next: { total: 6 }
What is a simple way to do this with pure RxJS streams?
Use multicast
Rx.Observable.range(1, 3)
.multicast(new Rx.Subject(), (shared)=> {
return Rx.Observable.merge(shared, shared.reduce((acc, x)=>acc+x,0))
})
.subscribe(x=>console.log(x))
As an alternative, you could avoid using share() and making two Observable chains and make just a single chain:
Observable.range(1, 3)
.concat(Observable.of(null))
.scan((obj, curr) => {
if (curr) {
obj.acc.push(curr);
}
obj.curr = curr;
return obj;
}, { acc: [], curr: 0 })
.map(obj => obj.curr === null
? { total: (obj.acc.reduce((acc, curr) => acc + curr, 0)) } // count total
: obj.curr // just return current item
)
.subscribe(console.log);
This prints the result you're expecting:
1
2
3
{ total: 6 }
Even though using share() looks very simple be aware that it in fact you subscribe to the source Observable twice. In practise maybe it's not a problem for you depending on what source Observable you'll use.
Try this and see that each number is printed twice:
let source = Observable.range(1, 3).do(console.log).share();
How about?
let source = Observable.range(1, 3).share();
let totalOb = source
.reduce((total, value) => total + value, 0);
source
.concat(totalOb)
.subscribe( value => console.log(`Next: ${value}`) );
Output:
Next: 1
Next: 2
Next: 3
Next: 6
You can use throw and catch to separate data and summary.
let source = Observable.range(1, 3).share();
let totalOb = source
.reduce((total, value) => total + value, 0)
.mergeMap(total => Observable.throw(total));
source
.concat(totalOb)
.subscribe(
value => console.log(`Next: ${value}`),
value => console.log(`Total: ${value}`)
);
Output:
Next: 1
Next: 2
Next: 3
Total: 6

Connecting multiple stages of parallel synths, with array of buses, in superCollider

When I have 2 stages of multiple parallel synths, I am able to connect it with an array of buses. (Thanks to Dan S for the answer to a previous question). When there is a 3 stage, this doesn't seem to work.
(
SynthDef(\siny, { arg freq, outBus=0; Out.ar( outBus, SinOsc.ar(freq!2,0,0.2) ) } ).send(s);
SynthDef(\filter, { arg cFreq,q=0.8, inBus, outBus=0; Out.ar( outBus, BPF.ar(In.ar(inBus), cFreq!2, 1/q ) ) } ).send(s);
)
(
var z = [100,500,1000,1500,200];
~sourceOut = z.collect{ Bus.audio(s) };
~sineOut = z.collect{ Bus.audio(s) };
~sine_Group = ParGroup.new;
~myGroup = ParGroup.new;
{
z.do({ arg val, index; Synth( \siny, [\freq: val, \outBus: ~sourceOut[index]], ~sine_Group ) });
z.do({ arg val, index; Synth.after(~sine_Group, \filter, [\inBus: ~sourceOut[index], \outBus: ~sineOut[index],\cFreq: 200, \q: 20 ], ~myGroup) });
z.do({ arg val, index; Synth.after(~myGroup, \filter, [\inBus: ~sineOut[index], \cFreq: 200, \q: 20]) });
}.play;
)
Another harm that I am doing here is, everytime I stop and run the synth, new instances of busses are created and eventually run out of audio buses. How can I solve this?
There are a number of issues with your code (some irrelevant with your question):
a. send(s) is a reminiscent of the past, these days simply add is preferable - you may consult the documentation for their differences.
b. it's not a good practice to mix local variables var with environmental ones (such as ~sourceOut) unless there is some good reason.
c. .collect(func) (or directly collect{/*function body here*/}) when send to an array results to another array where each element is the result of the func with the respective element of the original array passed as an argument. So here:
var z = [100,500,1000,1500,200];
~sourceOut = z.collect{ Bus.audio(s) };
~sineOut = z.collect{ Bus.audio(s) };
you don't use the original numbers anywhere, you just create two arrays containing 5 Audio buses each. Directly doing so would be less confusing and more explicit:
~sourceOut = Array.fill(5,{Bus.audio(s)});
~sineOut = Array.fill(5,{Bus.audio(s)});
d. there's no point in calling z.do three times.. you can call it just once and put the rest in the same function.
e. check you routings... You have two parallel groups and you're merely saying that I want these synths on that group and these others after that other group.. What you really want to say is that I want these Synths after those Synths on that group.
f. you don't free your Buses or your Synths when done... this is a memory leak :)
g. you call play on a function that want to evaluate really... you should call value instead.
This code does produces sound and cleans up everything when don, note however that the result is pretty boring because you've filtered all the higher-end.
s.waitForBoot({ // this is a routine
var frequencies = [100,500,1000,1500,200];
var sources = Array.fill(frequencies.size,{Bus.audio(s)});
var filters = Array.fill(frequencies.size,{Bus.audio(s)});
var parGroups = Array.fill(2,{ParGroup.new});
var sineSynths;
var firstOrderFilters;
var secondOrderFilters;
SynthDef(\siny, {
arg freq, outBus=0;
Out.ar( outBus, SinOsc.ar(freq!2,0,0.2));
}).add;
SynthDef(\filter, {
arg cFreq,q=0.8, inBus, outBus=0;
Out.ar( outBus, BPF.ar(In.ar(inBus), cFreq!2, 1/q ) )
}).add;
s.sync; // wait for the synthdefs to load
frequencies.do{ arg freq, index;
sineSynths = sineSynths.add( Synth(\siny, [\freq: freq.postln, \outBus: sources[index]],
parGroups[0])); // create synths and add them in the sineSynths Array
firstOrderFilters = firstOrderFilters.add(
Synth.after(sineSynths[index], \filter, [\inBus: sources[index],
\outBus: filters[index], \cFreq: 200, \q: 20 ], parGroups[1]));
secondOrderFilters = secondOrderFilters.add(
Synth.after(firstOrderFilters[index], \filter, [\inBus: filters[index],
\outBus: 0, \cFreq: 200, \q: 20 ], parGroups[1]));
};
10.wait; // wait 10 seconds;
// clean up everything
sineSynths.do{arg i; i.free};
firstOrderFilters.do{arg i; i.free};
secondOrderFilters.do{arg i; i.free};
sources.do{arg i; i.free};
filters.do{arg i; i.free};
});

Resources