I am trying to resume main stream same like onErrorContinue in java reactor core
Java example
Flux.range(1, 5)
.flatMap(n -> (n == 3) ? Mono.error(new Throwable("StoppedError")) : Mono.just(n))
.onErrorContinue((throwable, o) -> { System.out.println("error with " + o); })
.subscribe(System.out::println, System.out::println, System.out::println)
// 1
// 2
// error with 3
// 4
// 5
How can i do it in RXJS ? Thank you
Until Now already tried
Observable.range(1, 5)
.flatMap(v => v == 3 ? Observable.throwError(new Error("Stopped")): Observable.of(v))
.subscribe(...logs)
You will need to place the catchError operator on the inner observable. throwing the error and provide an error value, like -1
import { of, throwError, range } from "rxjs";
import { map, flatMap, catchError } from "rxjs/operators";
range(1, 5)
.pipe(
flatMap(v =>
(v == 3 ? throwError(new Error("Stopped")) : of(v)).pipe(
catchError(err => of(-1))
)
)
)
.subscribe(console.log);
Stackblitz: https://stackblitz.com/edit/rxjs-dnz4kx?devtoolsheight=60
In RxJS continuing after an error is like continuing after completing. It doesn't make semantic sense. Once an RxJS stream errors, it is done. That is the observable contract. complete() and error() emissions signal the end of an observable.
Without this in the observable contract, operators like retry could be disastrous. Also, RxJS streams aren't just dataprocessing and there's no standard for errors after transforming a stream with an operator.
Catching errors without streams
consider this non-stream error example:
for(let i = 0; i < 5, i++){
if(i !== 3) console.log(i);
else throw(new Error("Failed on " + i));
}
Here are two ways to handle this error:
1: try-catch outside the loop
try{
for(let i = 0; i < 5, i++){
if(i !== 3) console.log(i);
else throw(new Error("Failed on " + i));
}
}catch(e){
console.log(e);
}
2: try-catch inside the loop
for(let i = 0; i < 5, i++){
try{
if(i !== 3) console.log(i);
else throw(new Error("Failed on " + i));
}catch(e){
console.log(e.message);
}
}
If you wanted to try-catch outside the loop and then continue to print 4 and 5, that would be difficult. You'd need to rework the way errors are handled. They'd need a standard that said "This error was thrown in a loop and caused as a direct result of the current value in the loop, therefore it might make sense to continue the loop with the next value."
When you start nesting function calls inside of loops and so on, it becomes an increasingly bizarre tangle to even understand what "continue" after an error even means.
Consider that number 2 handles the error inside the loop and then lets the loop continue on. For number 2, we can throw a new error. Print a different number. Do nothing. We can even break out of the loop early without throwing an error. We have much more control because of where we are in the control flow of the program.
In number 1 above, once the loop failed, it was done. The control flow went outside of the loop. If you wanted to mimic a continue, you could start a new loop with the next number as its starting value.
function loopToFive(start = 0){
for(let i = start; i < 5, i++){
if(i !== 3) console.log(i);
else throw(new Error("Failed on " + i));
}
}
try{
loopToFive()
}catch(e){
console.log(e.message)
loopToFive(4)
}
This looks like catch and continue, but it's really just catch and recreate.
Catching Errors with Streams
Streams are just loops done asynchronously so that we can abstract away the time between values. Just like the example above, where you handle an error dramatically changes your ability to decide what happens next.
If you want to catch an error and continue, you can either catch it near the source and manage the control flow or you can catch it later and decide if/what part of the stream to re-create to continue.
Streams have built-in operators to retry, so the options are there. You can re-create onErrorContinue() for your case.
Related
In the following code variable 'checkNumber' is not incrementing to 1 even after 'if' block get executed and so that Break is not working where i need to break the loop
var checkNumber =0
for (let i = 0; i < totalRowCountAllocPrj; i++){
allocationObjects.getAllocationStatusfromGrid(i).then(text => {
appAllocStatus = Cypress.$(text).text()
cy.log("Allocation Status :" + appAllocStatus)
if(appAllocStatus == userData.approvalReservedAllocStatus){
allocationObjects.getAppAllCheckBoxesfromGrid(i).click()
checkNumber=checkNumber+1
cy.log("index="+i)
}
else{
cy.log("Project is not Reserved")
}
})
cy.log("number="+checkNumber)
if(checkNumber==1)
{
break
}
The variable checkNumber gets incremented inside an asynchronous command, but the loop is running synchronously.
You can't use break but you should be able to stop executing the commands with the inverse check.
Since checkNumber never should go above 0, it's more sensible to use a boolean
let found = 0
for (let i = 0; i < totalRowCountAllocPrj; i++) {
cy.then(() => {
if (!found) {
allocationObjects.getAllocationStatusfromGrid(i).then(text => {
appAllocStatus = Cypress.$(text).text()
if (appAllocStatus === userData.approvalReservedAllocStatus) {
allocationObjects.getAppAllCheckBoxesfromGrid(i).click()
found = true
}
})
}
})
}
BTW you should be able to rewrite allocationObjects.getAllocationStatusfromGrid() to directly search for userData.approvalReservedAllocStatus and get rid of the loop altogether.
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.
I need to exit the loop, if one of my queue elements satisfies a condition. putting break results in a compile error. what is the best way to execute the loop parallel
//code
std::deque<element> my_elements;
std::for_each(std::execution::par_unseq, my_elements.begin(), my_elements.end(),
[&](auto v) {
if (v.attribute == something)
{
break;
}
});
I'm pretty sure that won't work. Why? because break is a statically scoped operation; i.e, the destination for the break is determined at compile time. Putting it into a lambda (w the loop external to the lambda) makes it impossible for the compiler to see where to "break to".
Example w/o parallelism:
vector<int> v {1,2,3,4,5,6,7,8,9};
std::for_each(v.begin(), v.end(),
[](int i) { cout << i << ' '; if (i == 5) break; });
cout << endl;
gives me the following error:
so.cpp:10:45: error: 'break' statement not in loop or switch statement
[](int i) { cout << i << ' '; if (i == 5) break; });
To answer your question; I don't think there's a good way to break out of for_each in a parallel algorithm. For the non-parallel case, you could throw an exception, but that won't work in the parallel case.
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 was wondering why the following code (in coffeescript) will not retry as expected.
Rx = require 'rx'
count = 0
functToTest = (cb) ->
console.log "count is", count
count++
if count is 1
cb(new Error('some error'))
else if count is 2
cb(null,2)
else if count is 3
cb(null,3)
else
cb(null,4)
source = Rx.Observable.fromNodeCallback(functToTest)()
onNext = (value) ->
console.log value
onError = (err) ->
console.log err
onCompleted = ->
console.log "done"
retryableSrc = source.retry(3)
retryableSrc.subscribe(onNext, onError, onCompleted)
It will output following messages and quit
count is 0
[Error: some error]
I had thought this is might because fromNodeCallback() return a hot observable. But a test as below show it is NOT.
Rx = require 'rx'
count = 0
functToTest = (cb) ->
console.log "count is", count
count++
if count is 1
cb(new Error('some error'))
else if count is 2
cb(null,2)
else if count is 3
cb(null,3)
else
cb(null,4)
source = Rx.Observable.fromNodeCallback(functToTest)()
onNext = (value) ->
console.log value
onError = (err) ->
console.log err
onCompleted = ->
console.log "done"
retryableSrc = source.retry(3)
setTimeout ( -> ), 1000
If it was a hot observable, the program above should have printed some "count is 0" message. But in reality the program just waits 1 second and quits.
It actually is hot, or goes hot when you first subscribe to it.
Inside of fromNodeCallback is Rx.Observable.create(...).publishLast().refCount() meaning that when you first subscribe it will execute the method, print count then emit an error. The error will be caught downstream by retry, which will resubscribe thrice only to received the cached error, which it will finally emit itself.
You can fix it by using flatMap
ncb = Rx.Observable.fromNodeCallback(functToTest);
source = Rx.Observable.just(ncb).flatMap((fn) -> fn());