Как предотвратить завершение RxJS observable при ошибке?

#angular #rxjs #angular-reactive-forms

#angular #rxjs #angular-реактивные формы

Вопрос:

У меня есть угловая реактивная форма, которая выполняет простой запрос GET. Если, например, я должен получить какую-то ошибку HTTP. Теперь observable todo$ завершен, я больше не могу изменять параметры поиска и нажимать, чтобы повторить поиск. Я создал демонстрационную версию stackblitz, в демо-версии я добавил свою подписку внутри инициализатора, и если в потоке возникает ошибка, я ее улавливаю. После того, как я поймаю это, я снова вызываю инициализатор. Это работает, но кажется неправильным способом обработки ошибок.

Я настроил форму так, чтобы я мог отменять предыдущие HTTP-запросы.

 export class AppComponent implements OnDestroy {
  todos;
  loading = false;
  useFakeURL = false;
  todoSub$: Subscription;
  todo$: BehaviorSubject < string > = new BehaviorSubject < string > ('');

  @ViewChild('searchInput') searchInput: ElementRef;

  constructor(private provider: ExampleService) {
    this.init();
  }

  ngOnDestroy() {
    this.todoSub$.unsubscribe();
  }

  search() {
    const value = this.searchInput.nativeElement.value;
    this.todo$.next(value);
    this.useFakeURL = !this.useFakeURL;
  }

  private init(): void {
    this.todoSub$ = this.todo$.pipe(
        filter(val => !!val),
        tap(() => {
          this.loading = true;
        }),
        switchMap(() => this.provider.query(this.todo$.getValue(), this.useFakeURL)),
        catchError(error => {
          this.todo$.next('');
          this.init();
          return of([]);
        }),
      )
      .subscribe(
        todos => {
          this.loading = false;
          this.todos = todos;
        },
        err => {
          this.loading = false;
          this.todos = err;
        }
      );
  }
}

export class ExampleService {

  constructor(private http: HttpClient) {}

  query(todo, useFakeURL: boolean) {
    if (todo === 'all') {
      todo = '';
    }
    const url = useFakeURL ? 'poop' : `https://jsonplaceholder.typicode.com/todos/${todo}`;
    return this.http.get(url);
  }
}  
 <div class="container">
  <input #searchInput type="text" placeholder="Enter a number or enter 'all' to get all todos">
  <button (click)="search()">Get Todos</button>
</div>
<ul *ngIf="!loading amp;amp; todos amp;amp; todos.length">
  <li *ngFor="let todo of todos">
    <pre>
      {{todo | json}}
    </pre>
  </li>
</ul>
<pre *ngIf="!loading amp;amp; todos amp;amp; !todos.length">
  {{todos | json}}
</pre>
<div *ngIf="loading" class="loader">... LOADING</div>  

Комментарии:

1. Вы проверили retry ? Подробнее смотрите в этой статье Обработка ошибок в RxJS

2. Я не ищу запрос на немедленную повторную попытку, просто хочу иметь возможность изменить значение ввода и выполнить поиск снова.

3. Попробуйте поместить catchError внутрь switchMap

4. @Kos это работает! спасибо, я предположил, что перехватываю ошибку не в том месте.

Ответ №1:

HTTP-запросы Angular неизменяемы. Observable возвращается от клиента, выполняющего фактический вызов, когда вы подписываетесь на него. Сколько раз вы подписываетесь, столько запросов будет сделано. Это так называемая «Холодная наблюдаемость»

Если вам нужно изменить запрос, вам нужен совершенно новый observable из HttpClient.

Комментарии:

1. Понял неизменяемую часть, но у меня есть тема поведения, на которую я подписываюсь. Это никогда не должно завершаться, но это происходит, и когда я снова нажимаю поиск, ничего не происходит. может быть, причина в том, где я улавливаю ошибку? если вы закомментируете строки 45 и 46, вы увидите, что теперь нажатие кнопки поиск ничего не дает.

2. Вы не можете закомментировать 46, поскольку должен быть возвращен observable.

3. извинения означали 44 и 45.

4. Но, честно говоря, я не уверен, что работает не так, как вы хотели бы, чтобы это работало. Если я изменяю входные данные, службе передается новое значение. Как и ожидалось. Тогда что не так?

5. Поскольку я повторно инициализирую подписку с помощью this.init(); , если вы прокомментируете ` this.todo $.next(«); this.init();`, это завершит наблюдаемое

Ответ №2:

Как упоминалось @kos, я перехватывал ошибку не в том месте. Поскольку я переключился на новый observable, я должен перехватывать ошибку внутри switchMap . ОБНОВЛЕНО-ДЕМО

 search(term: Observable < string > ) {
  return term.pipe(
    filter(val => !!val),
    distinctUntilChanged(),
    debounceTime(500),
    // attached catchError to the inner observable here.
    switchMap(term => this.searchTodo(term).pipe(catchError(() => {
      return of({
        error: 'no todo found'
      });
    }))),
  );
}