【问题标题】:Use data from one observable in another and then return result as other observable在另一个可观察对象中使用数据,然后将结果作为其他可观察对象返回
【发布时间】:2021-02-13 23:14:56
【问题描述】:

我正在解决这个问题:

申请流程:

我必须调用第一个 API 端点(为简单起见,我们称之为 EP-A),它将 Blob 作为主体,将文件类型作为请求参数。它通过调用自动生成的类来执行

  uploadFile$Response(params?: {
    fileType?: 'USER_AVATAR' | 'UNKNOWN' | 'DELIVERY_LOGO' | 'PAYMENT_LOGO' | 'ITEM_PICTURE';
    body?: { 'file'?: Blob }
  }): Observable<StrictHttpResponse<FileUploadResponse>> {

    const rb = new RequestBuilder(this.rootUrl, FileControllerService.UploadFilePath, 'post');
    if (params) {
      rb.query('fileType', params.fileType, {});
      rb.body(params.body, 'application/json');
    }

    return this.http.request(rb.build({
      responseType: 'blob',
      accept: '*/*'
    })).pipe(
      filter((r: any) => r instanceof HttpResponse),
      map((r: HttpResponse<any>) => {
        return r as StrictHttpResponse<FileUploadResponse>;
      })
    );
  }

StrictHttpResponse&lt;T&gt; 只是一个包含“通用”主体的接口(因此您可以检索具有由 swagger 定义的结构的数据,从该结构生成此方法)。

然后是 FileUploadResponse 的结果,它是一个类似的对象

{
 uuid: string,
 time: Timestamp

 ...
 Other members omitted for simplicity
 ...
}

被发送到另一个 EP(我们称之为 EP-B)紧接着 EP-A 调用返回一个值,EP-B 将下面的对象作为主体,将当前记录的人员作为路径变量。

{
 uuid: string
}

所以在调用 EP-B 之前,应该解析来自 EP-A 的结果(在这种情况下,应该获取 uuid 字段并将其放入一个新对象中以供 EP-B 调用)

再次通过具有与上述签名相似的签名的生成方法(为简单起见,我将省略它)。

如果一切正常,我想让来电者知道这一点。如果有任何失败(这两个 EP 调用中的任何一个),我想让它知道调用此方法以做出某种反应(显示警报,以某种方式更改页面,...)

我现在的方法不完整,我不知道如何“连接”这两个 Observables,我读过有关 mergeMap、flatMap 等的信息,但我不确定如何在我的情况下使用它。

  updateUserAvatar(avatar: Blob): Observable<boolean> {

    return new Observable<boolean>((observer) => {

      // Calling EP-A
      this.avatarFormChangeRequestSubscription = this.fileControllerService.uploadFile$Response({
        fileType: 'USER_AVATAR',
        body: {
          file: avatar
        }
      })
        .subscribe((response: StrictHttpResponse<FileUploadResponse>) => {
            // Handle returned UUID and somehow pass it into an observable belog
            console.log(response);
          },
          (error: any) => {
            observer.error(error);
          });

      // Creating object for EP-B calling
      const avatarUpdateParams = {
        id: 1, // Just dummy ID for now, will be dynamically changed
        body: {
          avatarUUID: '' // the UUID from observable response above should be placed here
        }
      };

      // Calling EP-B
      this.avatarFormChangeRequestSubscription = this.userControllerService.updateUserAvatar$Response(avatarUpdateParams)
        .subscribe((response: StrictHttpResponse<string>) => {
          // Handle successfull avatar upload (change the "Logged user" object avatar to change it everywhere etc
          console.log(response);
          observer.next(true);
        },
          (error: any) => {
            observer.error(error);
          });
    });
  }

最后,我还想添加 “用例”流程,以从用户角度了解我想要实现的目标:

用户上传他的照片,该照片首先上传到BE端的文件系统(并与数据库记录链接),然后将该文件链接到他的个人资料作为他的个人资料图片。

【问题讨论】:

    标签: javascript angular typescript rxjs


    【解决方案1】:

    您可以使用rxjs 来完成。类似的东西可能有效:

    this.fileControllerService.uploadFile$Response({
        fileType: 'USER_AVATAR',
        body: {
          file: avatar,
        },
      })
      .pipe(
        tap((responseOfFirstApiCall: StrictHttpResponse<FileUploadResponse>) => {
          // Do whatever you want here, but you might not need that since you get the response below as well (in the flatMap)
          // Handle returned UUID and somehow pass it into an observable belog
          console.log(response);
        }),
        flatMap(
          (responseOfFirstApiCall: StrictHttpResponse<FileUploadResponse>) => {
            // Creating object for EP-B calling
            const avatarUpdateParams = {
              id: 1, // Just dummy ID for now, will be dynamically changed
              body: {
                avatarUUID: '', // the UUID from observable response above should be placed here
              },
            };
    
            return this.userControllerService.updateUserAvatar$Response(avatarUpdateParams);
          }
        ),
        tap((responseOfTheSecondApiCall: StrictHttpResponse<string>) => {
          // Handle successfull avatar upload (change the "Logged user" object avatar to change it everywhere etc
          console.log(response);
          observer.next(true);
        }),
        catchError((err: any) => of(err))
      )
      .subscribe(); // Empty subscribe() call to trigger the http request. Not needed if you get the result somewhere else (eg if your method return an observable that you want to handle the result somewhere else)
    
    

    flatMap() 与 mergeMap 相同。随心所欲地更改它,有很多选项,例如 map 或 switchMap,您应该了解它们,因为它们很有用。

    基本上,pipe 允许您链接函数,如果出现错误,则触发 catchError。

    提示:请注意,pipe 中的内容在您的 api 调用结果之前执行。因此,如果您想在获得结果之前对结果做一些事情,那么请考虑rxjs:

    服务

    getUser(id: string) {
      return this._http.get<any>(url).pipe(
        map(result => result.email), // Return only the email
      );
    }
    

    组件:

      ngUnsubscribe = new Subject();
    
      ngOnInit() {
        this._userService.getUser(1)
        .pipe(takeUntil(this.ngUnsubscribe)) // Don't forget to unsubscribe !
        .subscribe(email => console.log('email = ', email))
      }
    
      ngOnDestroy() {
        this.ngUnsubscribe.unsubscribe();
        // or
        // this.ngUnsubscribe.next();
        // this.ngUnsubscribe.complete();
      }
    

    【讨论】:

    • 您好,感谢您的回答,它就像一个魅力。我对此有一个问题。是否可以将该 UUID 存储在此合并映射链中的某个位置并在整个“管道通过”时使用它? (即使第二次调用更新头像也成功了) 用法如下:更新我的 localStorage 变量 user-avatar。在此操作之后,我想在 2 个地方更新此头像。在组件中,我将其称为“链”(这应该很容易)并更新包含头像且未连接到此方法调用的标题组件。
    • 不客气 :) 假设这段代码在服务中。如果您只需要在包含此代码的函数中重用 UUID,那么您可以声明一个变量并将 UUID 存储在其中,您将能够在“链”的每个步骤中使用它。现在,如果您想在服务外部、其他功能或任何您想要的地方使用它,那么在您的服务中声明您的变量(然后您就可以在组件中调用this.myService.myVariable)。请记住,默认情况下,服务是单例的,因此您的数据在应用程序的生命周期内不会更改。
    猜你喜欢
    • 2017-06-02
    • 2017-07-12
    • 2019-02-18
    • 2020-06-03
    • 2021-10-31
    • 1970-01-01
    • 1970-01-01
    • 2022-08-19
    • 1970-01-01
    相关资源
    最近更新 更多