处理后端服务器错误的可观察转换

Observable transformation to handle backend server error

抱歉 newbie/dump 关于 RxJS 的问题,刚开始学习 Angular。

所以我创建了一个异步验证器静态方法,它调用一个 API 服务方法来检查数据库中用户名的唯一性。下面的代码片段在后端服务器启动且 运行 时有效。如果不是,它会打印一条错误日志消息,我应该在演示组件中处理它。

在处理此类泛型用例以将 Observable<SuccessResponse>Observable<ErrorResponse> 转换为 Observable<ValidationErrors | null> 的转换步骤中缺少什么。

验证者

static usernameUnique(service: ApiService): AsyncValidatorFn {
    return (control: FormControl): Observable<ValidationErrors | null> => {
        return service.isUsernameTaken(control.value as string)
            .pipe(
                map((response: SuccessResponse) => response.data ? { taken: true } : null)
            );
    };
}

ApiService

isUsernameTaken(username: string): Observable<SuccessResponse | ErrorResponse> {
    return this.httpCient.get('/backend/account/checkUnique', { params: { username: username } })
                .pipe(catchError(this.handleError));
}

handleError(error: HttpErrorResponse) : Observable<ErrorResponse> {
    // return specific payload of ErrorResponse based on error.status
}

提前致谢。

我不确定您 运行 遇到了什么问题。如果您尝试将映射解析为验证器内的 ValidationErrors,则需要检查类型。在此示例中,我返回一个名为 "apiError" 的错误以区别于 "taken" 错误,但我不确定您选择如何在您的应用程序中处理这种情况。

我假设 ErrorResponse 包含一个 error 字段,并且 SuccessResponse 包含一个 data 字段。

static usernameUnique(service: ApiService): AsyncValidatorFn {
  return (control: FormControl): Observable<ValidationErrors | null> => {
    return this.isUsernameTaken(control.value as string)
      .pipe(
        map(response => {
          if ('error' in response) { // response is ErrorResponse
            return { apiError: response.error };
          }
          return response.data ? { taken: true } : null;
        })
      );
  };

如果您使用 new ErrorResponse()new SuccessResponse() 构建实际的 class 实例,您可以使用 intanceof 而不是使用 in 运算符检查字段。例如if (response instanceof ErrorResponse).

或者,您可以将错误冒泡回验证器,并在另一个 catchError 中处理响应。我更喜欢这种方式,因为 api.

的响应没有条件逻辑

验证器

static usernameUnique(service: ApiService): AsyncValidatorFn {
  return (control: FormControl): Observable<ValidationErrors | null> => {
    return this.isUsernameTaken(control.value as string)
      .pipe(
        map(response => response.data ? { taken: true } : null),
        catchError((err: ErrorResponse) => of({ apiError: err.error }))
      );
  };
}

ApiService

isUsernameTaken(username: string): Observable<SuccessResponse> {
  return this.http.get('/backend/account/checkUnique', { params: { username } })
    .pipe(
      map(response => response as SuccessResponse), // maps the api response to SuccessResponse
      catchError(this.handleError) // maps the error to ErrorResponse
    );
}

handleError(error: HttpErrorResponse): Observable<never> {
  let errorResponse: ErrorResponse;
  // specific payload of ErrorResponse based on error.status

  return throwError(errorResponse);
}