javascriptrxjsretrywhen

Retry, then ignore error and get source value rxjs


Error or no error, I want to get into the subscribe success handler after retrying. To test, comment out the setTimeout line. This will make it so that checkforText$ always throws an error. I want to ignore the final error, and still return the editor to the subscribe success handler.

https://codepen.io/Spankid/pen/gOgVZEE

var editor = { innerText: "" }
var editorOpened$ = of(editor) // observe editor object

function checkforText(){
    return new Promise((resolve,reject) => {
        console.log('checking for text',editor)
    
        if (editor.innerText == ""){
            reject('No text');
        } else {
            resolve(editor)
        }
    })
}

var checkForText$ = defer( () => from(checkforText())) // observe text on editor object

// comment out this line out to test
setTimeout( _ => { editor.innerText = 'testing' }, 2000) 

editorOpened$.pipe( 
    switchMap(editor => 
        checkForText$.pipe(
            retryWhen(errors => {
                console.log ('no text found... retrying')
                return errors.pipe( 
                    delay(1000), 
                    take(3),
                )
            }),     
         )
     )  
).subscribe(editor => {
    console.log('FINISH CHECKING', editor)  
}, err => console.log('ERROR ',err))
    

Output when text found (setTimeout not commented out):

checking for text {innerText: ""}
no text found... retrying
checking for text {innerText: ""}
checking for text {innerText: "testing"}
FINISH CHECKING {innerText: "testing"}

Target output when no text found (comment out setTimeout):

checking for text {innerText: ""}
no text found... retrying
checking for text {innerText: ""}
checking for text {innerText: ""}
checking for text {innerText: ""}
FINISH CHECKING {innerText: ""}

Actual output when no text found (comment out setTimeout):

checking for text {innerText: ""}
no text found... retrying
checking for text {innerText: ""}
checking for text {innerText: ""}
checking for text {innerText: ""}

I tried adding catchError after the retryWhen in hopes of making it into subscribe success handler. But it still does not. Any ideas?


Solution

  • It's not possible with retryWhen.

    The retryWhen operator completes after retrying for given attempts (in this case, specified by take(3) along with delay(1000)). So, if during the retries, your source observable which is checkForText$ does not emit a resolved promise or a non-error value, the pipeline consisting of checkForText$ and retryWhen will not emit anything and ultimately the stream will complete post retries.

    There's no way to get into the success handler after that unless you specifically make the checkForText$ to emit some non-error value during the retries.

    So, if you add a complete callback in your observer like below,

    editorOpened$.pipe( 
        switchMap(editor => 
            checkForText$.pipe(
                retryWhen(errors => {
                    console.log ('no text found... retrying')
                    return errors.pipe( 
                        delay(1000), 
                        take(3),
                    )
                }),     
             )
         )  
    ).subscribe(editor => {
        console.log('FINISH CHECKING', editor)  
      }, 
      (err) => console.log('ERROR ',err), 
      () => console.log('I'm done!') //The complete handler
    );
    

    Your case when no text found with setTimeout commented will return following -

    checking for text {innerText: ""}
    no text found... retrying
    checking for text {innerText: ""}
    checking for text {innerText: ""}
    checking for text {innerText: ""}
    I'm done!
    

    You can however achieve this using retry but I'm not sure how delay can be introduced just with retry.

    editorOpened$
      .pipe(
        switchMap(editor =>
          checkForText$.pipe(
            retry(3),
            catchError(errors => of(editor))
          )
        )
      )
      .subscribe(
        editor => {
          console.log("FINISH CHECKING", editor);
        },
        err => console.log("ERROR ", err),
        () => console.log("I'm done!")
      );
    

    This will produce:

    checking for text {innerText: ""}
    checking for text {innerText: ""}
    checking for text {innerText: ""}
    checking for text {innerText: ""}
    FINISH CHECKING {innerText: ""}
    I'm done!