使用可观察的异步/等待是一个好的实践吗?

我使用角2公共 http 返回一个可观测的,但我面临的问题是,我的代码喜欢一个网格当我使用嵌套的可观测调用:

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


})
})
})

现在我想使用异步/等待来避免这种情况,但是异步/等待只适用于承诺。我知道可观测可以转换为无极,但我知道,这不是一个好的做法。那我该怎么办?

顺便说一句,如果有人能给我一个示例代码来解决这个问题,那就太好了

100410 次浏览

按顺序链接可观测数据,正如您希望在代码中所做的那样

关于您的代码示例,如果您想要链接可观测数据(在前面的发出之后触发另一个) ,那么为此使用 flatMap(或 switchMap) :

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

与嵌套相比,这是更好的做法,因为这将使事情更清楚,并帮助您避免回调地狱,观察和承诺本来应该有助于防止摆在首位。

此外,考虑使用 switchMap而不是 flatMap,基本上它将允许“取消”其他请求,如果第一个发出一个新的值。如果触发其余部分的第一个可观察事件是某个按钮上的某个单击事件,那么使用它非常方便。

如果你不需要你的各种请求轮流等待对方,你可以使用 forkJoinzip启动他们一次,看到 @ Dan Macak 的回答的细节和其他见解。


角度的“异步”管道和可观测数据一起工作得很好

关于可观测量和角度,你可以完美地使用 | async管道在一个角度模板,而不是订阅在您的组件代码中的可观测量,以获得这个可观测量发出的值


ES6异步/等待和承诺代替可观察?

如果您不想直接使用 Observer,那么您可以简单地在 Observer 上使用 .toPromise(),然后使用一些异步/等待指令。

如果应该只返回一个结果(基本 API 调用就是这种情况) ,那么可观察的结果可以看作是一个完全等价的结果。

However, I'm not sure there is any need to do that, considering all the stuff that Observable already provide (to readers : enlightening counter-examples are welcome!) . I would be more in favor of using Observables whenever you can, as a training exercise.


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

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

函数的实际操作有点复杂,因为它并不复杂 实际上是一个“操作符”,而是一个特定于 RxJS 的 订阅一份可观察的报告,然后用一个承诺把它包裹起来 将解析为最后发射的观测值一旦 也就是说,如果可观测发射了 值“ hi”,然后等待10秒钟才完成,返回的 承诺将等待10秒钟之前解决“嗨”。如果观察到的 永远无法完成,那么无极就永远无法解决。

注意: 除非您是 处理一个期望得到一个毕业证书的 API,比如异步等待

(强调我的)


你要求的例子

顺便说一下,如果有人能给我一个示例代码来解决这个问题就太好了 这里使用了异步/等待: 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对观测数据所做的那样)

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.
}

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

承诺与可观测的混合

我个人比较喜欢 不要把承诺和可观察到的结果混为一谈——这是在使用可观察对象的异步等待时得到的,因为即使它们看起来相似,它们也非常不同。

  • 承诺总是异步的,可观察的未必如此
  • 承诺只代表1个值,可观察值为0、1或许多个
  • 承诺的使用非常有限,你不能例如取消它们(把 ES 下一个提议放在一边) ,可观察对象在使用中要强大得多(例如,你可以管理与它们的多个 WS 连接,尝试使用承诺)
  • 它们的 API 差别很大

角度承诺的运用

虽然有时两者都可以使用,特别是 使用 Angular 我认为应该考虑尽可能的使用 RxJS,但原因是:

  • 很大一部分的 Angular API 使用的是可观测数据 (路由器,http...) ,所以有一种使用 RxJS 的方式是顺应而不是逆流的(没有双关语的意思) ,否则人们将不得不一直转换为承诺,同时弥补 RxJS 提供的失去的可能性
  • Angular 拥有强大的 async管道,它可以组成您的整个应用程序的数据流,您过滤,组合和做任何修改,而不中断来自服务器 不需要发送或订阅的数据流。这样,你就不需要打开数据或者把它分配给一些辅助变量,数据只是从服务通过 Observables 直接流到模板,这很漂亮。

有些情况下,虽然 承诺依然闪耀。例如,我在 Rxjs TypeScript 类型中缺少的是 单身的概念。如果您正在创建一个供其他人使用的 API,那么返回 Observer 并不能说明所有问题: 您将收到1个值,多个值,还是仅仅完成?你必须写评论来解释它。另一方面,承诺在这种情况下有更清晰的合同。它总是用1个值解析,或者用错误拒绝(当然,除非它永远挂起)。

一般来说,在你的项目中,你肯定不需要只有承诺或者只有可观察的东西。如果您只想用某个值来表示某个 已经完成了(删除用户、更新用户) ,并且希望在不将其集成到某个流中的情况下对其作出反应,那么  承诺是更自然的方式。此外,使用 async/await使您能够按顺序编写代码,从而极大地简化了代码,因此,除非您需要对传入值进行高级管理,否则您可以继续使用  。


回到你的例子

因此,我的建议是 拥抱 RxJS 和 Angula 的力量r。回到您的示例,您可以编写如下代码(@Vayrex 的想法的学分) :

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


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

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

在这里使用 Observable.zip也会得到同样的结果,forkJoinzip的不同之处在于前者只发出内部观测值的最后一个值,后者结合了 Observables 内部的第一个值,然后是第二个值等。


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

对于流来说,可观测的东西是很好的,例如: 行为主体。但是对数据的单个调用(例如 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 调用似乎有些过头了。即使需要对检索到的数据进行处理,您仍然可以使用 getSomething ById 函数中的 RxJS 操作符并返回最终结果。

异步/等待的明显优势在于,它更易于阅读,而且不需要跳转到链式调用。

因为 toPromise在2022年已经被废弃了。我想展示另一种使用 await的方法。我发现这种方法可以使代码更具可读性,而不是使用长而复杂的 rxjs 管道。这对于 http 请求特别有用,因为只有一个响应,并且您通常希望在执行其他操作之前等待响应。


更新

我最初的解决方案是可行的,但是 rxjs 基本上具有相同的函数: firstValueFrom()

来自文件:

async function execute() {
const source$ = interval(2000);
const firstNumber = await firstValueFrom(source$);
console.log(`The first number is ${firstNumber}`);
}

原始解决方案

如果您有可观察的内容,则可以将其包装在承诺、订阅中,并在订阅完成时进行解析。

getSomething(): Promise<any> {
return new Promise((resolve, reject) => {
this.http
.get('www.myApi.com')
.subscribe({
next: (data) => resolve(data),
error: (err) => reject(err),
});
});
}

现在我们可以在 async函数中等待响应

  async ngOnInit() {
const data = await this.getSomething();
//Do something with your data
}

现在,我们可以对数据执行大量复杂的操作,对于不是 rxjs 向导的人来说,它将更具可读性。如果你有三个相互依赖的后续 http 请求,它们看起来像:

  async ngOnInit() {
const first = await this.getFirst();
const second = await this.getSecond(first);
const third = await this.getThird(second);
}