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

Tha*_*ynh 34 javascript promise observable typescript angular

我正在使用angular 2常见的http来返回一个Observable,但是当我使用嵌套的Observable调用时,我遇到的问题是我的代码喜欢网格:

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

       })
   })
})
Run Code Online (Sandbox Code Playgroud)

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

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

Pac*_*ac0 48

按顺序链接Observable,就像在代码中一样

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

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

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

另外,考虑使用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
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,您可以同时启动所有请求,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.
}
Run Code Online (Sandbox Code Playgroud)

  • 如果有人会添加Observable zip和forkJoin示例,那将是很好的.当请求可以并行完成时可以使用它,例如嵌套订阅不需要来自prev请求的数据. (2认同)
  • `forkJoin`和`zip`是好主意,前者甚至更多,因为它只发出内部Observables的最后一个值.我将在答案中详细说明. (2认同)
  • 答案是干净定性的 (2认同)

Dan*_*cák 13

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

混合承诺和观察

我个人喜欢混合使用Promises和Observables - 这是你在使用异步等待与Observables时得到的,因为即使它们看起来相似,它们也是非常不同的.

  • 承诺总是异步,Observables不一定
  • Promise只代表1个值,Observables 0,1或者很多
  • 承诺使用非常有限,你不能这样.取消他们(抛开ES未来的建议),观测量所以在其使用更强大的(可以管理与之例如多个WS连接,尝试与承诺)
  • 他们的API差别很大

Angular中Promise的使用

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

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

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

通常,您的项目中绝对不需要只有Promises或只有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]) => ...)
Run Code Online (Sandbox Code Playgroud)

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

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


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

  • `subscribe`的第二个参数是错误回调,或者如果你没有明确订阅或想要在错误处理方面有更多的自由,你可以在`forkJoin`之后调用`catch`并将错误处理程序作为第一个参数.在那里你可以将错误映射到其他值,例如. (3认同)

Chr*_*ton 11

由于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}`);
}
Run Code Online (Sandbox Code Playgroud)

原创解决方案

如果您有一个可观察的对象,您可以将其包装在一个承诺中,进行订阅,并在订阅发出时解析。

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

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

  async ngOnInit() {
    const data = await this.getSomething();
    //Do something with your data
  }
Run Code Online (Sandbox Code Playgroud)

现在我们可以对数据执行大量复杂的操作,对于不是 rxjs 向导的人来说,它将更容易阅读。如果您有三个相互依赖的后续 http 请求,它将如下所示:

  async ngOnInit() {
    const first = await this.getFirst();
    const second = await this.getSecond(first);
    const third = await this.getThird(second);
  }
Run Code Online (Sandbox Code Playgroud)


Gla*_*cus 8

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

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

然后,您可以像这样简单地调用它:

async someFunc(): Promise {
    console.log(await getSomethingById(1));
}
Run Code Online (Sandbox Code Playgroud)

RxJS 非常强大,但是将其用于简单的 api 调用似乎太过分了。即使您需要处理检索到的数据,您仍然可以使用 getSomethingById 函数内的 RxJS 运算符并仅返回最终结果。

async/await 的明显优点是阅读起来更清晰,并且您不需要跳过链式调用。

  • 是的,绝对是。您可以创建异步生成器函数,它们返回 AsyncEnumerables,并且您可以执行 `for wait (const i of items())` 等。/sf/answers/2958941191/ (2认同)