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
Related
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.
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())
)
I'm trying to create a custom retryWhen strategy which attempts to retry N times with X delay in-between and fail afterwards. To some extent the learnrxjs.io example is exactly what I'm looking for.
Unfortunately there is an issue with this code which I can't seem to figure how to resolve.
In my case, the observable can fail randomly - you can have 2 successful attempts and then 2 unsuccessful attempts. After a while the subscription will automatically complete, because the retryAttempts will exceed the maximum although that has not happened in practice.
To better understand the issue I've created a StackBlitz
The response will be:
Attempt 1: retrying in 1000ms
0
1
Attempt 2: retrying in 2000ms
Attempt 3: retrying in 3000ms
0
1
We are done!
But it should actually be
Attempt 1: retrying in 1000ms
0
1
Attempt 1: retrying in 1000ms <-- notice counter starts from 1
Attempt 2: retrying in 2000ms
0
1
Attempt 1: retrying in 1000ms <-- notice counter starts from 1
0
1
Attempt 1: retrying in 1000ms <-- notice counter starts from 1
Attempt 2: retrying in 2000ms
0
1
... forever
I feel like I'm missing something here.
I think the example given in the docs is written for an Observable that only emits once and then completes, such as an http get. It is assumed that if you want to get more data then you will subscribe again which will reset the counter inside genericRetryStrategy. If, however, you now want to apply this same strategy to a long-running observable whose stream won't complete unless it gives an error (such as you have with interval()), then you'll need to modify genericRetryStrategy() to be told when the counter needs to be reset.
This could be done a number of ways, I have given a simple example in this StackBlitz based off of what you said you were trying to accomplish. Note that I also changed your logic slightly to more match what you said you were trying to do which is have '2 successful attempts and then 2 unsuccessful attempts'. The important bits though are modifying the error object that is thrown into genericRetryStrategy() to communicate the current count of failed attempts so it can react appropriately.
Here is the code copied here for completeness:
import { timer, interval, Observable, throwError } from 'rxjs';
import { map, switchMap, tap, retryWhen, delayWhen, mergeMap, shareReplay, finalize, catchError } from 'rxjs/operators';
console.clear();
interface Err {
status?: number;
msg?: string;
int: number;
}
export const genericRetryStrategy = ({
maxRetryAttempts = 3,
scalingDuration = 1000,
excludedStatusCodes = []
}: {
maxRetryAttempts?: number,
scalingDuration?: number,
excludedStatusCodes?: number[]
} = {}) => (attempts: Observable<any>) => {
return attempts.pipe(
mergeMap((error: Err) => {
// i here does not reset and continues to increment?
const retryAttempt = error.int;
// 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);
}
console.log(
`Attempt ${retryAttempt}: retrying in ${retryAttempt *
scalingDuration}ms`
);
// retry after 1s, 2s, etc...
return timer(retryAttempt * scalingDuration);
}),
finalize(() => console.log('We are done!'))
);
};
let int = 0;
let err: Err = {int: 0};
//emit value every 1s
interval(1000).pipe(
map((val) => {
if (val > 1) {
//error will be picked up by retryWhen
int++;
err.msg = "equals 1";
err.int = int;
throw err;
}
if (val === 0 && int === 1) {
err.msg = "greater than 2";
err.int = 2;
int=0;
throw err;
}
return val;
}),
retryWhen(genericRetryStrategy({
maxRetryAttempts: 3,
scalingDuration: 1000,
excludedStatusCodes: [],
}))
).subscribe(val => {
console.log(val)
});
To me this is still very imperative, but without understanding the problem you are trying to solve more deeply, I can't currently think of a more declarative approach...
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$;
}
)
)
I just completed the Programming a Guessing Game chapter of The Rust Programming Language. I now want to add validation for the number of digits but couldn't find a pleasing way to do it.
I am able to achieve it by replacing
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("Please type a number.");
continue;
},
};
with
let guess = guess.trim();
if guess.parse::<u32>().is_err() || guess.chars().count() > 2 {
println!("Please type a number from 1 to 100.");
continue;
}
let guess: u32 = match guess.parse() {
Ok(num) => num,
Err(_) => continue, // this will never happen
};
The way I would prefer to do this is by somehow chaining matches, but I couldn't figure it out.
You don't need to chain match. You just need a different pattern:
let guess: u32 = match guess.trim().parse() {
Ok(num # 1...100) => num,
_ => {
println!("Please type a number within 1-99.");
continue;
},
};
This pattern means "If it's Ok and it has something bigger than 0 but smaller than 100, return its content, otherwise print message and continue."
You can chain matches like this:
let guess: u32 = match guess.trim().parse() {
Ok(num) => match guess.chars().count() {
1 | 2 => num,
_ => {
println!("Please type a number from 1 to 100.");
continue;
}
},
Err(_) => {
println!("Please type a number.");
continue;
},
};
However, checking the string length is not what you really want. For example "00023" parses to "23" but has a strength length of 5.
Since you have access to num in this context, you can match on its value directly!
// ...
Ok(num) => match num {
1..=100 => num,
_ => {
println!("Please type a number from 1 to 100.");
continue;
}
},
// ...