将 Observable 与 async/await 一起使用是一个好习惯吗?

IT技术 javascript angular typescript promise observable
2021-03-08 17:48:45

我正在使用返回 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个回答

按顺序链接 Observables,就像你想在你的代码中做的那样

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

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

与嵌套相比,这是更好的实践,因为这将使事情更清晰并帮助您避免回调地狱,而 Observable 和 Promise 应该首先帮助防止。

此外,考虑使用switchMap而不是flatMap,如果第一个请求发出新值,它基本上将允许“取消”其他请求。例如,如果触发其余部分的第一个 Observable 是按钮上的某个点击事件,则使用起来很好。

如果您不需要您的各种请求轮流等待彼此,您可以使用forkJoinzip同时启动它们,有关详细信息和其他见解,请参阅@Dan Macak 的回答


Angular 'async' 管道和 Observables 可以很好地协同工作

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


ES6 async / await 和 Promises 而不是 Observables ?

如果你不觉得直接使用 Observable,你可以简单地.toPromise()在你的 Observable 上使用,然后是一些 async/await 指令。

如果您的 Observable 应该只返回一个结果(就像基本 API 调用的情况一样),则 Observable 可以被视为与 Promise 相当。

但是,考虑到 Observable 已经提供的所有内容,我不确定是否需要这样做(对读者来说:欢迎提供启发性的反例!)。我更倾向于尽可能使用 Observables 作为训练练习。


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

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

toPromise 函数实际上有点棘手,因为它并不是真正的“运算符”,而是一种 RxJS 特定的订阅 Observable 并将其包装在Promise中的方法。一旦 Observable 完成,promise 将解析为 Observable 最后发出的值这意味着如果 Observable 发出值“hi”然后在它完成之前等待 10 秒,返回的 Promise 将在解析“hi”之前等待 10 秒。如果 Observable 永远不会完成,那么 Promise 永远不会解决。

注意:使用 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()这应该会更有效率,因为没有一个调用依赖于彼此的结果。(就像forkJoinObservable 一样)

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 retrieve promises results,
    // in res[0], res[1], res[2] respectively.
}
实际上我只是想在@angular/http 中使用Http 提供的promise,但是这个module不支持拦截器(因为我使用JWT),所以我必须使用返回Observable 的@angular/common/http。它导致了我的问题。
2021-04-20 17:48:45
实际上,关于您的最后一个代码片段,最好await Promise.all()与所有这些调用一起使用,否则它们甚至必须在触发请求之前等待先前的调用。
2021-04-26 17:48:45
forkJoinzip是好主意,前者更多,因为它只发出内部 Observables 的最后一个值。我将在答案中详细说明。
2021-05-02 17:48:45
是的。Promise.all 和 Observable.zip (forkJoin)。
2021-05-09 17:48:45
如果有人会添加 Observable zip 和 forkJoin 示例会很好。当请求可以并行完成时可以使用它,例如嵌套订阅不需要来自上一个请求的数据。
2021-05-17 17:48:45

由于@Pac0 已经很好地阐述了各种解决方案,我将添加稍微不同的角度。

混合 Promise 和 Observable

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

  • Promises 总是异步的,Observables 不一定
  • Promises 只代表 1 个值,Observables 0、1 或许多
  • Promise的用途非常有限,你不能例如。取消它们(将 ES 的下一个提案搁置一旁),Observables 在它们的使用中更加强大(例如,您可以管理多个 WS 与它们的连接,尝试使用 Promises)
  • 他们的API差异很大

在 Angular 中使用 Promise

现在,即使有时两者都使用是有效的,尤其是在 Angular 中,我认为应该考虑尽可能使用 RxJS原因如下:

  • Angular API 的很大一部分使用 Observables(路由器,http ...),所以通过使用 RxJS 一种与流相配而不是逆流(没有双关语意),否则人们将不得不在弥补时一直转换为 Promises对于 RxJS 提供的失去的可能性
  • Angular 具有强大的async管道,它允许组合您的整个应用程序流数据流,您可以对其进行过滤、组合和进行任何您想做的修改,而无需中断来自服务器的数据流,而无需进行任何更改或订阅这样,你不需要解包数据或将其分配给一些辅助变量,数据只是从服务通过 Observables 直接流向模板,这很漂亮。

但在某些情况下Promise 仍然可以发光比如我所缺少rxjstypescript类型是概念如果你正在创建一个供其他人使用的 API,返回 Observable 并不能说明一切:你会收到 1 个值,很多,还是只是完成?你必须写评论来解释它。另一方面,Promise 在这种情况下有更清晰的契约。它将始终以 1 个值解析或因错误而拒绝(当然,除非它永远挂起)。

一般来说,你的项目中绝对不需要只有 Promise 或 Observables。如果您只想用一个值表示某事已完成(删除用户、更新用户),并且您想对其做出react而不将其集成到某个流中,那么 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 个请求,一旦所有这些请求 Observables 都完成,订阅回调forkJoin将为您提供数组中的结果,如上所述,您可以手动订阅它(如示例中所示)或以声明方式执行此操作在模板中使用result$async管道。

使用Observable.zip将在这里得到你同样的结果之间的差异forkJoin,并zip在于,前者仅发出内部观测量的最后的值,后者结合了内观测量的第一个值,那么第二个值等。


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

这是flatMapPac0s 示例(第一个片段)中的方法。
2021-04-29 17:48:45
实际上我必须使用 resA 进行 serviceB.get() 调用。使用 Observable 最好的方法是什么?
2021-05-11 17:48:45
到第二个参数subscribe是错误回调,或者如果你没有明确订阅或希望有错误处理更多的自由,你可以调用catchforkJoin,错误处理程序作为第一个参数。例如,您可以在那里将错误映射到其他值。
2021-05-18 17:48:45
source$.switchMap(x => promiseDelay(x)) // 有效 .subscribe(x => console.log(x));
2021-05-20 17:48:45

Observables 非常适合流,例如: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 的明显优势在于它更清晰易读,并且您不需要跳过箍来链接调用。

@justin,在 C# 中?当然。你能在 TS/JS 中做到这一点吗?
2021-04-24 17:48:45
是的,绝对。您可以制作异步生成器函数,它们会返回 AsyncEnumerables,您可以执行for await (const i of items())等等。 stackoverflow.com/a/42270588/12958
2021-04-28 17:48:45
甚至流也比 Promise 更好。使用 AsyncEnumerable 并将它们实现为生成器函数。
2021-05-03 17:48:45