【问题标题】:How to avoid RxJs subscribe callback hell?如何避免 RxJs 订阅回调地狱?
【发布时间】:2019-02-24 04:51:42
【问题描述】:

我正在使用 Angular RxJs subscribe 进行 HttpClient 调用,然后使用第一个调用中的值进行另一个调用。在这种情况下,调用获取address object,然后我使用此对象进行调用。像这样:

@Injectable()
export class AddressService {
  constructor(private http: HttpClient) { }

  getById(addressId: string, userId: string) {
    return this.http.get(BACKEND_URL + 'getAddressById/' + [addressId, userId]);
  }
}
  
export class AddressModalComponent implements OnInit {
  constructor(private alertService: AlertService, private addressService: AddressService,           @Inject(MAT_DIALOG_DATA) public data: any, private dropdownService: DropdownService)

  ngOnInit() {
    this.addressService.getById(this.data.id, this.data.userId)
        .subscribe(
          (address: Address) => {
            this.dropdownService.getCidadesBrByEstado(address.name)
              .subscribe((cities: BrCity[]) => {
                this.cities = cities;
                this.address = address;
              },
              error => console.log(error));
          }, error => { this.alertService.error(error);
          }
        );
    }
  }
}

我试图避免多次订阅,我的代码中有很多这样的。我需要像Node.js promises 这样的Async/Await 方法,但在组件级别使用Observables。我对RxJs commands 不是很熟悉...有没有更好的方法可以只用一个subscribecatch 拨打多个电话?

【问题讨论】:

    标签: angular typescript rxjs observable


    【解决方案1】:

    尝试类似:

    import { map, switchMap } from 'rxjs/operators'
    
    this.addressService.getById(this.data.id, this.data.userId).pipe(
      switchMap(address => this.dropdownService.getCidadesBrByEstado(address.name).pipe(
        // this pass both cities and address to the next observable in this chain
        map(cities => ({ cities, address }))
      ))
    ).subscribe(({ cities, address }) => {
      this.cities = cities
      this.address = address
    })
    

    【讨论】:

    • switchMapmergeMapforkJoin 有什么区别?我不确定我是否可以switchMap 这样的每个订阅
    • RxJS 中的运算符是一个非常丰富的 API。您必须通过谷歌/阅读这些内容并在简单的示例中尝试它们以了解它们的工作原理。不是我能用几句话解释的。
    • 如需了解switchMapmergeMapconcatMapexhaustMap 之间的区别,请查看this article
    • 这个答案看起来就像回调地狱。它比问题中的代码更简洁。想象一下用 4-5 个连续的 http 请求来做这件事。它会向右扩展和向下扩展 switchMaps 和管道一样多,并产生典型的回调地狱视觉效果。
    • 好吧,switchMapmergeMapwhateverMap 实际上解决了可读性问题。我明白了。
    【解决方案2】:

    对于使用 RxJS 时的 angular,建议使用 Observable 类。要解决 RxJS 中的回调地狱,您可以使用 Observable 的 Operators api,例如 switchMap() 方法(针对不同场景的更多方法是 map()、concatMap()、...)。这是我使用 switchMap() 方法的示例:
    (1)我遇到的情况:我想订阅serviceC,但是serviceC需要订阅serviceB,而serviceB需要订阅serviceA

    const serviceA(params): Observable<any>;
    const serviceB(params): Observable<any>;
    const serviceC(params): Observable<any>;
    
    serviceA(paramsA).subscribe(
        serviceAResult => {
            serviceB(paramsB).subscribe(
                serviceBResult => {
                    serviceC(params).subscribe(
                        serviceCResult => {
                            // here is my logic code. Oh, Shit subscribe hell!
                        }
                    )
                }
            )
        }
    )
    

    (2)使用switchMap()方法优化代码结构

    const serviceB$ = serviceA(paramsA).pipe(
        switchMap(serviceAResult => {
            return serviceB(paramsB);
        })
    );
    
    const serviceC$ = serviceB$.pipe(
        switchMap(serviceBResult => {
            return serviceC(paramsC);
        })
    );
    
    serviceC$.subscribe(
        serviceCResult => {
            // here is my logic code.
        },
        error =>{
            // handle error
        }
    );
    

    关于处理callback hell的好帖子。

    【讨论】:

    • 虽然链接可能很方便,但最好在此处包含解决方案的精髓,以防链接脱机或移动。
    【解决方案3】:

    假设,您实际上并不关心流,您也可以在这种情况下将 Observables 转换为 Promise 并使用 async/await

    async ngOnInit(): Promise<void> {
      this.address = await this.addressService.getById(this.data.id, this.data.userId).toPromise();
      this.cities = await this.dropdownService.getCidadesBrByEstado(this.address.name).toPromise();
    }
    

    并确保您也发现了错误。以try catch 为例。

    【讨论】: