如何使用RxJS运算符避免多个嵌套订阅?

时间:2019-03-29 11:01:57

标签: angular rxjs6

我正在使用Angular进行文件加密和上载类。这些操作很多都是异步的,因此我编写的方法返回了RxJS Observable。

// 1.
private prepareUpload(file): Observable<T>;

// 2.
private encryptData(data, filekey): Observable<T>

// 3.
private uploadEncryptedData(formData, token, range): Observable<T>

// 4.
private completeUpload(updatedFilekey, token): Observable<T>

我想将此逻辑封装在公共upload(file)方法中,最终我使用了嵌套订阅,并且可以使用,但是我知道这是错误的,并且出于多种原因,RxJS中使用了反模式。这是代码的简化版本:

public upload(file) {
    const gen = this.indexGenerator(); // generator function

    this.prepareUpload(file).subscribe(values => {
    const [response, filekey, data] = values;

    this.encryptData(data, filekey).subscribe(encryptedDataContainer => {
      const formData = this.prepareEncDataUpload(encryptedDataContainer.data, file.name)
      const range = this.getRange(file.size, gen.next().value);

      this.uploadEncryptedData(formData, response.token, range).subscribe(() => {
        if (range.isFinalPart) {
            this.completeUpload(encryptedDataContainer.updatedFilekey, response.token).subscribe(console.log);
        }
      });

    });

  });

}

我无法使用多个RxJS运算符的组合来清除此代码。我的目标是避免嵌套订阅,而是在工作流完成时从公共upload()方法返回单个Observable。

谢谢!

4 个答案:

答案 0 :(得分:0)

您要在订阅前使用管道。管道允许您在流发出值之前对流中的值进行更改。另外,使用mergeMap展平订阅链。这里是概述。这不能提供完整的解决方案-付给我的钱还不够;)-但这足以为您指明正确的方向:

this.prepareUpload(file).pipe(
  tap(values => console.log('hello1', values)),
  map(values => [response, filekey, data]),
  tap(values => console.log('hello2', values)),
  mergeMap(() =>
      // essential to catchError else an HTTP error response will disable this effect - if it uses HTTP - catchError essentially prevents the stream from erroring in which case it will never emit again
      this.encryptData(data, filekey).pipe(
        map(res => res), // do something with res here if you want
        catchError(() => {
          return of(null)
        })
      )
    ),
    filter(res => !!res)
    // more mergemap stuff here
  ).subscribe(values => {
    console.log(values)
  })

提示:使用tap运算符在流向下传递值时使用console.log值

PS:未选中语法,可能缺少逗号或方括号或2

PPS:管道中的函数都是RxJS运算符

答案 1 :(得分:0)

您可以使用mergeMap rxjs运算符合并这些可观察对象,并摆脱嵌套订阅。

尽管有一个陷阱, 请注意,由于mergeMap一次维护多个活动的内部订阅,因此可能通过长期存在的内部订阅造成内存泄漏。

作为参考和示例: https://www.learnrxjs.io/operators/transformation/mergemap.html

答案 2 :(得分:0)

  

我认为将您的可观察物链接起来就可以做到,您可以通过   flatMap(mergeMap的别名)可能是-https://stackoverflow.com/a/37777382/9176461和   RxJS Promise Composition (passing data)

正如我在评论中所提到的,类似以下的内容应该起作用(伪代码):

public upload(file) {
    const gen = this.indexGenerator(); // generator function

    return Rx.Observable.just(file).pipe(
         mergeMap(this.prepareUpload),
         mergeMap(this.encryptData),
         mergeMap(this.prepareEncDataUpload),
         mergeMap(this.prepareEncDataUpload),
         .... )
}

答案 3 :(得分:0)

您可以使用RxJ中的mergeMapfilter运算符并链接您的呼叫。您将需要创建一些函数级变量以在链接过程中使用。

import { mergeMap, filter, catchError } from 'rxjs/operators`
public upload(file) {
    const gen = this.indexGenerator(); // generator function
    let range, token;
    this.prepareUpload(file)
      .pipe(
        mergeMap((values) => {
          const [response, filekey, data] = values;
          token = response.token;
          return this.encryptData(data, filekey);
        }),
        mergeMap(encryptedDataContainer => {
          const formData = this.prepareEncDataUpload(encryptedDataContainer.data, file.name)
          range = this.getRange(file.size, gen.next().value);

          return this.uploadEncryptedData(formData, token, range);
        }),
        filter(() => !!range.isFinalPart),
        mergeMap(() => {
          return this.completeUpload(encryptedDataContainer.updatedFilekey, token);
        })
        catchError((error) => {
          console.log(error);
          // handle the error accordingly.
        })
      )
      .subscribe(() => {
        console.log('success');
      });

  }