DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 968,547 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Masui Masanori
Masui Masanori

Posted on

[Angular][RxJS] I want to return immediatly when some operations are failed

Intro

When I write C#, I often write like below.

private async Task ExecuteExampleAsync()
{
    var result1 = await DoSomethingAsync1();
    if (result1 == null)
    {
        return;
    }
    var result2 = await DoSomethingAsync2();
    if (result2 == null)
    {
        return;
    }
    return await DoSomethingAsync3();
}
Enter fullscreen mode Exit fullscreen mode

I want to stop operations and return earlier when I get some invalid result from the pervious operation.

How I can do with RxJS?

I try it.

Environments

  • Angular ver.10.1.0-next.1
  • RxJS ver.6.6

empty, throwError, throw new Error

I think I can use three ways to stop operations.

Base sample class

In this time, I call some methods to try.

workflow-page.component.ts

import { Component, OnInit } from '@angular/core';
import { Observable, of, empty, throwError } from 'rxjs';
import { flatMap, catchError } from 'rxjs/operators';

@Component({
  selector: 'app-workflow-page',
  templateUrl: './workflow-page.component.html',
  styleUrls: ['./workflow-page.component.css']
})
export class WorkflowPageComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {
      // call methods
  }
}
Enter fullscreen mode Exit fullscreen mode

empty

Once "empty" is called, "complete" handler will be called immediately.

...
  ngOnInit(): void {
    console.log('-- Throw empty from first call --');
    this.executeEmpty(4);
    console.log('-- Throw empty from second call --');
    this.executeEmpty(3);
    console.log('-- End --');
  }
  private executeEmpty(startValue: number) {
    this.getEmpty(startValue)
      .pipe(
        flatMap(result => {
          console.log(`2nd execution: ${result}`);
          return this.getEmpty(result);
        }),
        catchError(error => {
          console.error(`catch: ${error}`);
          return of(error);
        })
      )
      .subscribe(result => console.log(`next: ${result}`),
        error => console.error(`error: ${error}`),
        () => console.log('complete'));
  }
  private getEmpty(lastValue: number): Observable<number> {
    if (lastValue > 3) {
      return empty();
    }
    return of(lastValue + 1);
  }
Enter fullscreen mode Exit fullscreen mode

result

-- Throw empty from first call --
complete
-- Throw empty from second call --
2nd execution: 4
complete
-- End --
Enter fullscreen mode Exit fullscreen mode

If the method uses "empty", it must handle the result of operations.

Because the caller("ngOnInit()" in this sample) only can know the operation is completed.

empty

throwError

...
  ngOnInit(): void {
    console.log('-- Throw throwError from first call --');
    this.executeThrowError(4);
    console.log('-- Throw throwError from second call --');
    this.executeThrowError(3);

    console.log('-- Throw throwError with catchError from first call --');
    this.executeThrowErrorWithCatchError(4);
    console.log('-- Throw throwError with catchError from second call --');
    this.executeThrowErrorWithCatchError(3);
    console.log('-- End --');
  }
  private executeThrowError(startValue: number) {
    this.getThrowError(startValue)
    .pipe(
      flatMap(result => {
        console.log(`2nd execution: ${result}`);
        return this.getThrowError(result);
      })
    )
    .subscribe(result => console.log(`next: ${result}`),
        error => console.error(`error: ${error}`),
        () => console.log('complete'));
  }
  private executeThrowErrorWithCatchError(startValue: number) {
    this.getThrowError(startValue)
    .pipe(
      flatMap(result => {
        console.log(`2nd execution: ${result}`);
        return this.getThrowError(result);
      }),
      catchError(error => {
        console.error(`catch: ${error}`);
        return of(error);
      })
    )
    .subscribe(result => console.log(`next: ${result}`),
        error => console.error(`error: ${error}`),
        () => console.log('complete'));
  }
  public getThrowError(lastValue: number): Observable<number> {
    if (lastValue > 3) {
      return throwError('Error from throwError');
    }
    return of(lastValue + 1);
  }
Enter fullscreen mode Exit fullscreen mode

result

-- Throw throwError from first call --
error: Error from throwError
...
-- Throw throwError from second call --
2nd execution: 4
error: Error from throwError
...
-- Throw throwError with catchError from first call --
catch: Error from throwError
...
next: Error from throwError
complete
-- Throw throwError with catchError from second call --
2nd execution: 4
catch: Error from throwError
...
next: Error from throwError
complete
-- End --
Enter fullscreen mode Exit fullscreen mode
  • When I don't add "catchError", "error" handler of "subscribe" will be fired.
    Alt Text

  • When I add "catchError", it will be fired and if it return "of(error)", "next" and "complete" handler of "subscribe" will be fired.
    Alt Text

So when I want to handle the invalid value on "catchError" or "error" handler, I can use "throwError".

throw new Error

How about throwing error, or when some errors are occurred in methods?

...
  ngOnInit(): void {
    console.log('-- Throw new Error from first call --');
    this.executeThrowNewError(4);
    console.log('-- Throw new Error from second call --');
    this.executeThrowNewError(3);
  }
  private executeThrowNewError(startValue: number) {
    this.getThrowNewError(startValue)
      .pipe(
        flatMap(result => {
          console.log(`2nd execution: ${result}`);
          return this.getThrowNewError(result);
        })
      )
      .subscribe(result => console.log(`next: ${result}`),
        error => console.error(`error: ${error}`),
        () => console.log('complete'));
  }
  // throw new Error
  public getThrowNewError(lastValue: number): Observable<number> {
    if (lastValue > 3) {
      throw new Error('Error from new Error()');
    }
    return of(lastValue + 1);
  }
Enter fullscreen mode Exit fullscreen mode

result

-- Throw new Error from first call --
ERROR Error: Error from new Error()
Enter fullscreen mode Exit fullscreen mode

Why I got only the first result?

It's because if errors are occurred in first execution, "catchError" and "error" handler won't be able to handle them.
Alt Text

So if I change the argument from 4 to 3, the result will be like below.

-- Throw new Error from first call --
2nd execution: 4
error: Error: Error from new Error()
...
Enter fullscreen mode Exit fullscreen mode

Alt Text

So I think the methods(functions) what returns Observable value shouldn't use "throw new Error".

They should notify caller some errors by "throwError".

Should I use "throwError" to notify?

I think if I choose from "empty", "throwError", "throw new Error" I prefer to "throwError".
I still don't know if I should use "throwError" to notify some operations are failed :<

So I will read more samples or documents.

Top comments (0)

πŸŒ™ Dark Mode?!

Turn it on in Settings