首页 > 解决方案 > 使用 concatMap 从链式 HTTP 调用返回 Observables

问题描述

在 Angular 9 服务类中,我使用 RxJS 链接了几个 HTTP 调用concatMap,使用第一个调用的输出作为第二个调用的输入:

getUserDetails(authorisationCode: string): Observable<UserInfo> {

this.getAuthorisationTokens(authorisationCode)
  .pipe(
    concatMap(authorisationTokens =>  this.getUserInfo(authorisationTokens.access_token)))
    .subscribe(
      (data: UserInfo) => {
        this.userInfo = data;
        console.log(JSON.stringify(this.userInfo));
        console.log(JSON.stringify(data));
      },
      (err: any) => console.log('Error getting user info details : ' + err),
      () => {
        console.log('Got user information: ' + this.userInfo);
      }
    );

return of(this.userInfo);
}

我想返回this.userInfo给调用者,我的第一个天真的想法是将它包装在 Observable ( return of(this.userInfo)) 中并像这样调用它:

export class LandingComponent implements OnInit {

  username: string;
  userFirstName: string;
  email: string;


  constructor(private route: ActivatedRoute, private userService: UserDataService) {
    this.authorisationCode = route.snapshot.queryParamMap.get('code');
    console.log('Code was ' + this.authorisationCode);
  }

  ngOnInit(): void {

    this.userService.getUserDetails(this.authorisationCode)
      .subscribe((data: UserInfo) => {
        this.userFirstName = data.given_name;
        this.username = data.preferred_username;
        this.email = data.email;

        console.log('Got: ' + this.userFirstName + ', ' + this.username + ', ' + this.email);
      });

  }
}

我可以从浏览器控制台看到对我的服务的调用正在成功并填充我的 object this.userInfo,但只有在我尝试使用它并得到一个未定义的错误之后:

Code was 2ffa40f9-5e71-4f29-8ddd-318e8d0b99bc
main-es2015.8df8d853b157ca70b40a.js:1 Getting authorisation tokens in exchange for authorisation code 2ffa40f9-5e71-4f29-8ddd-318e8d0b99bc
main-es2015.8df8d853b157ca70b40a.js:1 Header: [object Object]
main-es2015.8df8d853b157ca70b40a.js:1 Body: grant_type=authorization_code&redirect_uri=https://xxx/landing/&client_id=xxx&code=2ffa40f9-5e71-4f29-8ddd-318e8d0b99bc&client_secret=xxx
main-es2015.8df8d853b157ca70b40a.js:1 TOKEN endpoint: https://xxx.amazoncognito.com/oauth2/token

TOKEN endpoint: https://xxx.amazoncognito.com/oauth2/token
main-es2015.8df8d853b157ca70b40a.js:1 ERROR TypeError: Cannot read property 'given_name' of undefined

    ...

USERINFO endpoint https://xxx.amazoncognito.com/oauth2/userInfo
main-es2015.8df8d853b157ca70b40a.js:1 USERINFO endpoint https://xxx.amazoncognito.com/oauth2/userInfo
main-es2015.8df8d853b157ca70b40a.js:1 {"sub":"4bfd88a4-5439-4ad6-a399-71b02034dfa1","email_verified":"true","given_name":"Craig","family_name":"Caulfield","email":"craig.caulfield@xxx.com","username":"4bfd88a4-5439-4ad6-a399-xxx"}
main-es2015.8df8d853b157ca70b40a.js:1 Got user information: [object Object]

我试图应用以下问题,但我找不到将它们应用于我的案例的方法:

所以,我的异步想法是错误的。有什么我没有做过的明显的事情吗?

标签: angulartypescriptrxjs

解决方案


您可能会返回undefined,因为this.userInfo它是异步赋值的。一种正确的方法是返回 HTTP observable 并在控制器中订阅它。

我也已经将提取authorisationCode从钩子constructor移到了ngOnInit()钩子上。虽然通常ngOnInit()钩子会在constructor调用之后触发,但不能保证在触发钩子时变量this.authorisationCode已经被赋值。

尝试以下

服务

getUserDetails(authorisationCode: string): Observable<any> {
  return this.getAuthorisationTokens(authorisationCode)
    .pipe(
      concatMap(authorisationTokens =>  this.getUserInfo(authorisationTokens.access_token))
    );
}

控制器

constructor(private route: ActivatedRoute, private userService: UserDataService) { }

ngOnInit(): void {
  const authorisationCode = route.snapshot.queryParamMap.get('code');
  this.userService.getUserDetails(authorisationCode).subscribe(
    (data: UserInfo) => {
      this.userFirstName = data.given_name;
      this.username = data.preferred_username;
      this.email = data.email;

      console.log('Got: ' + this.userFirstName + ', ' + this.username + ', ' + this.email);
    },
    (err: any) => { console.log('Error getting user info details : ' + err) },
    () => { console.log('Got user information: ' + data); }
  );
}

推荐阅读