使用带有async / await的Observable是一个好习惯吗?

时间:2018-04-01 09:25:28

标签: javascript angular typescript promise observable

我使用的是返回Observable的angular 2 common http,但是当我使用嵌套的Observable调用时,我面临一个问题,即我的代码喜欢网格:

this.serviceA.get().subscribe((res1: any) => {
   this.serviceB.get(res1).subscribe((res2: any) => {
       this.serviceC.get(res2).subscribe((res3: any) => {

       })
   })
})

现在我想使用async / await来避免这种情况,但async / await只适用于Promise。我知道Observable可以转换为Promise,但据我所知,这不是一个好习惯。那我该怎么办呢?

顺便说一下,如果有人能给我一个示例代码来解决这个问题,那将很高兴:async / await:D

3 个答案:

答案 0 :(得分:32)

按顺序链接Observable,如您在代码中所做的那样

关于您的代码示例,如果您想链接Observables(在上一次发出后触发另一个),请使用flatMap(或switchMap)来实现此目的:

this.serviceA.get()
  .flatMap((res1: any) => this.serviceB.get())
  .flatMap((res2: any) => this.serviceC.get())
  .subscribe( (res3: any) => { 
    .... 
  });

与嵌套相比,这是一个更好的练习,因为这会使事情更清晰,并帮助你避免回调地狱,Observable和Promises应该首先帮助预防。

另外,考虑使用switchMap代替flatMap,基本上它会允许取消'如果第一个发出新值,则另一个请求。如果触发其余部分的第一个Observable是按钮上的某个点击事件,则可以使用。

如果您不需要依次等待各种请求,可以使用forkJoinzip一次启动所有请求,请参阅@Dan Macak answer's细节和其他见解。

Angular' async'管道和Observables一起工作

关于Observables和Angular,您可以在Angular模板中完美地使用| async管道,而不是在组件代码中订阅Observable,以获取此Observable发出的值

ES6 async / await和Promises而不是Observables?

如果您不想直接使用Observable,可以在Observable上使用.toPromise(),然后使用async / await指令。

如果您的Observable只返回一个结果(就像基本API调用那样),Observable可以看作与Promise完全等效。

但是,我不确定是否有必要这样做,考虑到Observable已经提供的所有东西(对读者来说:欢迎具有启发性的反例!)。作为一种训练练习,我会更倾向于随时使用Observable。

一些有趣的博客文章(还有很多其他文章):

https://medium.com/@benlesh/rxjs-observable-interop-with-promises-and-async-await-bebb05306875

  

toPromise函数实际上有点棘手,因为它不是   真的是一个“操作员”,而不是RxJS特有的手段   订阅一个Observable并将其包装在一个promise中。 承诺   将解析为Observable的最后一次发射值   Observable完成。这意味着如果Observable发出了   值“hi”然后在完成之前等待10秒,返回   承诺将在解决“hi”之前等待10秒。如果是Observable   永远不会完成,然后承诺永远不会解决。

     

注意:使用toPromise()是一个反模式,除非你是这样的   处理期望Promise的API,例如async-await

(强调我的)

您请求的示例

  顺便说一下,如果有人能给我一个示例代码来解决它会很好   这与async / await:D

示例如果您真的想要这样做(可能有一些错误,现在无法检查,请随时纠正)

// Warning, probable anti-pattern below
async myFunction() {
    const res1 = await this.serviceA.get().toPromise();
    const res2 = await this.serviceB.get().toPromise();
    const res3 = await this.serviceC.get().toPromise();
    // other stuff with results
}

如果您可以同时启动所有请求,await Promise.all()应该更高效,因为没有一个调用取决于彼此的结果。 (与forkJoin对Observables一样)

async myFunction() {
    const promise1 = this.serviceA.get().toPromise();
    const promise2 = this.serviceB.get().toPromise();
    const promise3 = this.serviceC.get().toPromise();

    let res = await Promise.all([promise1, promise2, promise3]);

    // here you can promises results,
    // res[0], res[1], res[2] respectively.
}

答案 1 :(得分:10)

由于@ Pac0已经详细阐述了各种解决方案,我只会添加稍微不同的角度。

混合承诺和可观察量

我个人更喜欢不混合Promises和Observables - 这是你在使用Asservables async await时得到的,因为即使它们看起来很相似,它们也是非常不同的。

  • Promise总是异步,Observables不一定
  • Promises仅代表1个值,Observables 0,1或许多
  • 承诺使用非常有限,你不能这样做。取消它们(搁置ES下一个提议),Observables在使用方面更加强大(你可以管理例如与它们的多个WS连接,尝试使用Promises)
  • 他们的API差别很大

在Angular中使用Promise

现在即使有时使用两者都是有效的,特别是和Angular一起我认为应该考虑尽可能地使用RxJS 。原因是:

  • Angular API的很大一部分使用Observables (路由器,http ...),因此使用RxJS可以使用而不是反对流(没有双关语),否则就会有转换为Promise,同时弥补RxJS提供的失去的可能性
  • Angular具有强大的async管道,允许组合流的整个应用程序数据流,您可以对其进行过滤,组合和执行任何修改,而不会中断来自服务器的数据流而无需单身需要订阅或订阅。这样,您不需要打开数据或将其分配给某些辅助变量,数据只是通过Observables直接流向模板,这很漂亮。

在某些情况下,承诺仍然可以发光。例如, rxjs TypeScript类型中缺少的是 single 的概念。如果您正在创建一个供他人使用的API,那么返回Observable并不是那么说:您会收到1个值,很多,还是只会完成?你必须写评论来解释它。另一方面,Promise在这种情况下有更清晰的合同。它将始终以1值解析或拒绝并出现错误(除非它当然永远挂起)。

通常,您绝对不需要在项目中只有Promise或只有Observable。如果你只想用一个完成(删除用户,更新用户)的值来表达,并且你想对它做出反应而不将它集成到某个流中,Promise是更自然的做法所以。此外,使用async/await使您能够以顺序方式编写代码,从而大大简化代码,因此除非您需要对传入值进行高级管理,否则您可以继续使用Promise。

回到您的示例

所以我的推荐是拥抱RxJS和Angula r的力量。回到你的例子,你可以编写如下代码(想法归功于@Vayrex):

this.result$ = Observable.forkJoin(
  this.serviceA.get(),
  this.serviceB.get(),
  this.serviceC.get()
);

this.result$.subscribe(([resA, resB, resC]) => ...)

这段代码将触发3个请求,一旦所有请求Observable完成,订阅回调到forkJoin将获得数组中的结果,如上所述,您可以手动订阅它(如在示例中)或使用模板中的result$async管道以声明方式执行此操作。

使用Observable.zip会在这里得到相同的结果,forkJoinzip之间的区别在于前者只发出内部Observable的最后一个值,后者组合了第一个值内部的Observables,然后是第二个值等。

修改:由于您需要先前HTTP请求的结果,请在@ Pac0的回答中使用flatMap方法。

答案 2 :(得分:1)

Observable 非常适合流,例如:BehaviorSubject。但是对于数据的单一调用(例如 http.get()),您最好使服务调用本身异步。

async getSomethingById(id: number): Promise<Something> {
    return await this.http.get<Something>(`api/things/${id}`).toPromise();
}

然后,您可以简单地这样称呼它:

async someFunc(): Promise {
    console.log(await getSomethingById(1));
}

RxJS 非常强大,但将其用于简单的 api 调用似乎过于矫枉过正。即使您需要处理检索到的数据,您仍然可以使用 getSomethingById 函数中的 RxJS 操作符并返回最终结果。

async/await 的明显优势在于它更清晰易读,而且您不需要跳过箍来进行链式调用。