Angular 2调用多个异步方法

Posted

技术标签:

【中文标题】Angular 2调用多个异步方法【英文标题】:Angular 2 calling multiple async methods 【发布时间】:2017-08-08 01:21:12 【问题描述】:

我正在构建一个移动应用程序,现在我正在处理身份验证。在我点击我的主页之前,我需要在我构建的 API 上点击各种端点,然后才能向用户显示数据。

在 Postman 中测试时,所有端点都返回了正确的数据,但是当我在我的应用程序中使用它时,我在第二次异步调用中得到了一个 null 值。

我确信这与进行这些调用的顺序有关,因此我只是在寻求一些帮助,以了解如何在开始另一个调用之前正确等待一个调用完成。

public login() 

this.showLoading();

this.userService.getUserIdFromUserName(this.registerCredentials.username) // WORKS
  .subscribe(
    res => 
      console.log(res);
      localStorage.setItem("UserId", res.toString());
    ,
    err => 
      console.log(err);
    );

this.userService.getEmployeeIdFromUserId(localStorage.getItem("UserId")) // THIS RETURNS NULL 
  .subscribe(
    res => 
      console.log(res);
      localStorage.setItem("EmployeeId", res.toString());
    ,
    err => 
      console.log(err);
    );

this.authService.login(this.registerCredentials)
  .subscribe(

    data => 
      this.loading.dismissAll();
      console.log('User logged in successfully! ', data);
      this.nav.push(TabsPage);
      localStorage.setItem("Username", this.registerCredentials.username);
      localStorage.setItem("isLoggedIn", "true");
    ,

    error => 
      this.loading.dismissAll();
      this.showAlert("Uh oh!", "Something went wrong. Please re-enter your login credentials or check your connection.");
      console.log(error);
    );
  

【问题讨论】:

标题为Angular2,但关键字为angularjs。您可以考虑修改两者之一。 你应该在this answer找到你需要的东西。 【参考方案1】:

您的原始代码存在导致此错误的错误。您的代码中有三个调用,我将它们称为 A)、B) 和 C):

A) this.userService.getUserIdFromUserName(this.registerCredentials.username) // WORKS

B) this.userService.getEmployeeIdFromUserId(localStorage.getItem("UserId")) // THIS RETURNS NULL 

C) this.authService.login(this.registerCredentials)

关于 RXJS,您需要了解的是 cold Observable(代表启动异步操作所需的所有信息)和 hot Observable(这是一个异步操作已经开始的 Observable)。

这三个调用 A)、B) 和 C) 只是构建冷的 observables,它们在你调用 .subscribe() 的那一刻就开始了。因此,当 B) 建成时,A) 已经开始但尚未完成。因此对localStorage.getItem("UserId") 的调用将返回null,因为A) 尚未调用其订阅者的next 回调。

所以你想要做的是让 B) 等待 A)。此外,与其将某些东西填充到全局状态(localStorage)中,不如将结果从 A)流向 B)。输入.mergeMap() 运算符:

this.userService.getUserIdFromUserName(this.registerCredentials.username) // WORKS
  .map(res => res.toString())
  .do(userId => localStorage.setItem("UserId", userId)) // cleanly separate side-effects into .do() calls
  .mergeMap(userId => this.userService.getEmployeeIdFromUserId(userId))
  .map(res => res.toString())
  .do(employeeId => localStorage.setItem("EmployeeId", employeeId))
  .subscribe(
    employeeId => 
      console.log(employeeId);      
    ,
    err => 
      console.log(err);
    );

关于 rxjs 的好处是错误处理可以一直通过 Observable 链进行。 如果您可以并行执行 C),请查看 .forkJoin().

最后,如果您需要对 .mergeMap() 的解释,请查看以下答案:SwitchMap vs MergeMap in the #ngrx example

【讨论】:

+1:很好的答案。这里真的很容易理解并且整体非常干净的代码。显然这与你无关,但我希望 RxJS 操作符有更好的名字。人们想摆脱selectMany(这是一个好名字),所以他们选择了flatMap(这也是一个好名字)。两者都保留了与集合的类比。 mergeMap 什么都不做。它可能在排序方面更具描述性,或者缺少它们,但读起来像是故意提供直觉,但它却惨遭失败。他们不妨将其重命名为 bind 并完成它。 问题:switchmap 不是更好的解决方案吗? (我知道区别,但仍然......)【参考方案2】:

这应该有效。不要忘记import 'rxjs/Rx'

this.userService.getUserIdFromUserName(this.registerCredentials.username)
  .map(res => res.toString())
  .do(userId => 
      console.log(res);
      localStorage.setItem("UserId", userId);
    )
  .flatMap(userId => 
       return this.userService.getEmployeeIdFromUserId(userId);
     )
  .do(res => 
      console.log(res);
      localStorage.setItem("EmployeeId", res.toString());
    )

【讨论】:

以上是关于Angular 2调用多个异步方法的主要内容,如果未能解决你的问题,请参考以下文章

Angular 2 测试 - 异步函数调用 - 何时使用

调用服务时异步调用不起作用 - Angular 8

如何使用构造函数上的异步调用注入服务,Angular 2

Angular 2订阅订阅另一个异步功能的服务

Angular 打字稿异步 HTTPClient 调用

Angular RxJs:针对异步数据流编程工具