Обработка ошибок Angular 7 HttpClient concatMap

#angular #rxjs #angular-httpclient

#angular #rxjs #angular-httpclient

Вопрос:

У меня есть некоторый код, который выполняет загрузку большого файла меньшими порциями. Вот что у меня есть:

 upload(project: Project): Observable<any> {

    var chunks = this.chunkFiles(project);

    var totalPercent = project.file
        .map(file => Math.ceil(file.size / this.chunkSize))
        .reduce((sum, curr) => sum   curr) * 100;

    var finishedPercent = 0;
    var currentPercent = 0;

    return from(chunks)
        .pipe(
            concatMap(request =>
            {
                return this.http.request(request)
                    .pipe(
                        retryWhen(error =>
                        {
                            return interval(1000)
                                .pipe(
                                    flatMap(count =>
                                    {
                                        if (count == 2)
                                        {
                                            return throwError(error);
                                        }
                                        return of(count);
                                    })
                                );
                        }));
            }),
            map(event =>
            {
                if (event.type === HttpEventType.UploadProgress)
                {
                    var progress = event as HttpProgressEvent;
                    currentPercent = Math.round(100 * progress.loaded / progress.total);
                    return {
                        type: event.type,
                        loaded: finishedPercent   currentPercent,
                        total: totalPercent
                    };
                }
                else if (event instanceof HttpResponse) 
                {
                    finishedPercent  = 100;
                    if (finishedPercent == totalPercent)
                    {
                        return event;
                    }
                }
            }),
            filter(response => response !== undefined)
        );
}
  

Предполагается, что он начнет загружать фрагменты файла, и как только одна из загрузок фрагмента завершится неудачей, она должна прекратиться, и ошибка должна распространиться на код, который вызывает мою функцию загрузки. Этот конкретный код выглядит следующим образом:

 onStartUpload = this.actions$
    .ofType(forRoot.START_UPLOAD).pipe(
    switchMap((action: forRoot.StartUpload) =>
        this.uploadService
            .upload(action.project).pipe(
            map((event) => {
                if (event.type === HttpEventType.UploadProgress) {
                    const progress = event as HttpProgressEvent;
                    const percentDone = Math.round(100 * progress.loaded / progress.total);
                    return new forRoot.UploadProgress(percentDone);
                } else if (event instanceof HttpResponse) {
                    return new forRoot.UploadSucceeded();
                } else {
                    console.log(event);
                }
            }),
            filter(a => a !== undefined),
            catchError(error => {
                if (error instanceof HttpErrorResponse amp;amp; error.status === 400) {
                    const message = this.getMessage(error);
                    return of(new forRoot.UploadFailed(message || "Bad request"));
                } else {
                    return of(new forRoot.UploadFailed("Network error"));
                }
            })))
        );
  

Проблема в том, что я получаю только сообщение «Ошибка сети», тогда как я ожидаю что-то со статусом ошибки 400 (поэтому я должен получать либо конкретное сообщение, либо «Неверный запрос».

Я предполагаю, что я неправильно обрабатываю ошибку при повторной попытке, но я попробовал несколько разных идей, и, похоже, ни одна из них не работает. Чего я не понимаю?

Отредактируйте, чтобы показать рабочий код без повторных попыток:

Вот код, который корректно обрабатывает сообщения об ошибках, однако в нем нет повторной попытки:

 return from(chunks)
    .pipe(
        concatMap(request =>
        {
            return this.http.request(request)
        }),
        map(event =>
        {
            if (event.type === HttpEventType.UploadProgress)
            {
                var progress = event as HttpProgressEvent;
                currentPercent = Math.round(100 * progress.loaded / progress.total);
                return {
                    type: event.type,
                    loaded: finishedPercent   currentPercent,
                    total: totalPercent
                };
            }
            else if (event instanceof HttpResponse) 
            {
                finishedPercent  = 100;
                if (finishedPercent == totalPercent)
                {
                    return event;
                }
            }
        }),
        filter(response => response !== undefined)
    );
  

Я предполагал, что есть что-то, что я могу добавить к вызову http.request, чтобы повторить указанное количество раз с задержкой между повторными попытками, а затем, если все они завершатся неудачей, выдать ошибку, аналогичную http.request . Похоже, что происходит то, что он переходит от кода concatMap к коду map. Я могу добавить вызовы console.log (событие) и увидеть сообщение об ошибке, возвращаемое с сервера. Я все еще довольно новичок в rxjs, поэтому, возможно, я не понимаю, но я ожидал, что после возникновения ошибки код карты не будет выполняться.

Ответ №1:

Проблема здесь:

 retryWhen(error => {     // << error is a stream
  return interval(1000)
    .pipe(
        flatMap(count => {
          if (count == 2){
              return throwError(error); // so here a stream is thrown
          }

          return of(count);
      })
    );
})
  

error аргумент — это не отдельная ошибка, а фактически наблюдаемая из всех возникающих ошибок.

Чтобы исправить это, вы должны вернуть observable, производный от error , например:

 retryWhen(errors$ =>
  errors$.pipe(
    delay(1000)
  )
)
  

Обновленный пример ограниченной обработки ошибок

 retryWhen(errors =>
  errors.pipe(
    // switchMap to add limiting logic
    switchMap((error, index) =>
      index == 3                      // if it is a 4th error
      ? throwError(error)             //  -- we rethrow it upstream
      : of(error).pipe( delay(1000) ) //  -- delay a retry otherwise
    )
  )
)
  

Вот пример повторной попытки с экспоненциальным возвратом.

И статья об обработке ошибок в rxjs (с примечанием о retryWhen досрочном завершении).

Надеюсь, это поможет

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

1. Я пробовал несколько разных альтернатив, но объединение (НИКОГДА) продолжает вызывать у меня проблемы. Ошибка заключается в следующем: ОШИБКА в src / app /upload/state/upload.service.ts(151,33): ошибка TS2345: аргумент типа ‘Observable<never>’ не может быть присвоен параметру типа ‘OperatorFunction<any, любой>’. Тип ‘Наблюдаемый<никогда>’ не обеспечивает соответствия подписи ‘(источник: Наблюдаемый<любой>): Наблюдаемый<любой>’.

2. @Dan, О, вероятно, это связано с тем, что оно concat импортировано из rxjs , тебе следует импортировать его из rxjs/operators . Это распространенная проблема с этим оператором, я забыл упомянуть. Есть фабрика concat , которая создает наблюдаемые, и оператор, который обрабатывает наблюдаемые. Нам нужен оператор.

3. Это исправило сообщение об ошибке, и я смог протестировать, но оно не работает. Похоже, что он проглатывает ошибку (пользователь не получает никакого сообщения об ошибке). Я собираюсь отредактировать вопрос, чтобы показать рабочий код, в котором нет повторных попыток.

4. @Dan, виноват, извини. Это действительно проглатывало ошибки. Я исправил это и обновил пример ограниченных повторных попыток. Добавьте это в свой код здесь return this.http.request(request).pipe( ...retryWhen... )