【问题标题】:Best way for multiple HTTP Request in AngularAngular中多个HTTP请求的最佳方式
【发布时间】:2021-02-25 12:42:19
【问题描述】:

我正在尝试一个一个地发送 2 个 HTTP 请求;如果第一个成功,则发送第二个,如果没有,则显示与第一个请求有关的相应错误消息。

我打算使用类似的东西,但不确定它是否是这种情况下的最佳选择:

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-root',
  templateUrl: 'app/app.component.html'
})
export class AppComponent {
  loadedCharacter: {};
  constructor(private http: HttpClient) {}

  ngOnInit() {
    this.http.get('/api/people/1').subscribe(character => {
      this.http.get(character.homeworld).subscribe(homeworld => {
        character.homeworld = homeworld;
        this.loadedCharacter = character;
      });
    });
  }
}

我有不同的要求,例如PUT 和 CREATE 也使用这种方法。我知道还有其他方法,例如forkjoinmergemap,但如果这个解决了我的问题似乎更具可读性。有什么想法吗?

【问题讨论】:

  • @Picci 好像很有用,我一定会读的。但是现在,我需要一个紧急的解决方案。有什么建议吗?
  • Here 是我写的关于处理嵌套可观察对象的简短答案。

标签: javascript angular typescript rxjs


【解决方案1】:

2 嵌套订阅绝不是可行的方法。我推荐这种方法:

this.http.get('/api/people/1').pipe(
  switchMap(character => this.http.get(character.homeworld).pipe(
    map(homeworld => ({ ...character, homeworld })),
  )),
).subscribe(character => this.loadedCharacter = character);

编辑:对于你的大学

this.http.get('/api/people/1').pipe(
  switchMap(character => this.http.get(character.university).pipe(
    map(university => ({ ...character, university})),
  )),
).subscribe(character => this.loadedCharacter = character);

甚至连锁大学和家乡的要求

this.http.get('/api/people/1').pipe(
  switchMap(character => this.http.get(character.homeworld).pipe(
    map(homeworld => ({ ...character, homeworld })),
    // catchError(err => of({ ...character, homeworld: dummyHomeworld })),
  )),
  switchMap(character => this.http.get(character.university).pipe(
    map(university => ({ ...character, university})),
  )),
).subscribe(character => this.loadedCharacter = character);

【讨论】:

  • 能否请您更新一下您对此方案的答案?首先你得到一个如上所述的人,然后通过调用另一个请求来更新他或她的大学名称?
  • 非常感谢。即使第一个调用出错,它是否会继续执行第二个调用?另一方面,如何处理每个调用的错误?
  • 顺便投票,感谢您的帮助。如果您能稍微解释一下它是如何工作的,我将不胜感激。问候。
  • 有了这个链,如果第一个调用失败,第二个调用将不会被执行。但是您可以在map 运算符之后添加catchError,我再次更新了我的答案,我只添加了一个示例作为评论。使用catchError,外部流将继续。
  • 很抱歉,我需要澄清一下。实际上,您发送 get 请求并将返回的 homeworld 添加到 character 数组中。真的吗?但实际上我想发送第一个请求添加,然后通过获取其 ID,发送另一个请求以添加另一条记录(假设添加一个人,然后自动添加默认教育信息作为小学)。那么,如果您不介意,您可以为此添加第 3 个场景吗?第二个对其他人也有用,保留它是个好主意。我认为您的方法非常有用,这就是我想使用它的原因:) 问候。
【解决方案2】:

您可以通过检查状态码来检查第一个请求是否成功:

  ngOnInit() {
    this.http.get('/api/people/1').subscribe((character: HttpResponse<any>) => {
      // here you should look for the correct status code to check, in this example it's 200
      if (character.status === 200) {
        this.http.get(character.homeworld).subscribe(homeworld => {
          character.homeworld = homeworld;
          this.loadedCharacter = character;
        });
      } else {
        // character is gonna contain the error
        console.log(character)
      }
    });
  }

【讨论】:

  • 嵌套 subscribe 不被认为是 RxJs 的惯用语
  • 感谢帮助,但我认为不建议使用这种方法。
【解决方案3】:

首先,您的代码可以运行,这很好 - 您可以保持原样,一切都会好起来的。

另一方面,有多种改进方法可以帮助您和您的同事将来:

  1. 尝试将与 http 相关的逻辑移动到服务中,而不是在组件中调用 http - 这将帮助您将代码拆分为与视图相关的逻辑和与业务/获取/转换相关的逻辑。
  2. 尽量避免嵌套subscribes - 不仅您忽略了Observables 的强大功能,而且还会将代码绑定到某个流程,而无法在应用程序的某处重用这些行。返回 Observable 可能会帮助您“共享”请求的结果或以某种方式对其进行转换。
  3. flatMap/mergeMapconcatMapswitchMap 以不同的方式工作,让您能够按照自己的方式控制行为。尽管对于http.get(),它们的工作方式几乎相似,但最好尽快开始学习这些组合运算符。
  4. 考虑一下在这种情况下您将如何处理错误 - 如果您的第一次调用将导致错误,会发生什么情况? Observables 有一个强大的机制来处理它们,而.subscribe 只允许您以一种方式处理错误。

一个使用switchMap的例子:

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-root',
  templateUrl: 'app/app.component.html'
})
export class AppComponent {
  loadedCharacter: {};
  constructor(private http: HttpClient) {}

  ngOnInit() {
    const character$ = this.http.get('/api/people/1').pipe(
      tap(character => this.characterWithoutHomeworld = character), // setting some "in-between" variable
      switchMap(character => {
        return this.http.get(character.homeworld).pipe(
            map(homeworld => {
                    return {
                        ...character,
                        homeworld: homeworld
                    }
                }
            )
        )
      }),
      catchError(errorForFirstOrSecondCall => {
        console.error('An error occurred: ', errorForFirstOrSecondCall);
        // if you want to handle this error and return some empty data use:
        // return of({});
        
        // otherwise: 
        throw new Error('Error: ' + errorForFirstOrSecondCall.message);
      })
);

    // you can either store this variable as `this.character$` or immediately subscribe to it like:
    character$.subscribe(loadedCharacter => {
        this.loadedCharacter = loadedCharacter;
    }, errorForFirstOrSecondCall => {
       console.error('An error occurred: ', errorForFirstOrSecondCall);
    })
  }
}


【讨论】:

  • 非常感谢您的详细解释。实际上这就是为什么我问这个问题,即使它会起作用。因此,在这种情况下,您是否可以通过使用您建议的一种方式来发布上面的代码,例如flatMap/mergeMap 等等?我更喜欢一种可读性最强的方式,以便我可以轻松地操纵结果。
  • 您获得了最多的支持,但建议一些用法会更好。提前致谢。
  • @PragDopravy 我添加了一个带有switchMap的示例
  • 对不起,但我不确定我是否很好地解释了这个场景。假设我有 2 个不同的 API 调用来更新数据,并且; 1. 在每个调用中,我都想获得该调用的响应并捕获与该调用相关的错误。 2. 如果第一次调用失败,我想在收到错误或检查响应数据后中断并且不执行第二次调用。所以,我认为使用switchMap 似乎更好。那么,您能否根据我在此处解释的情况更新您的答案?
  • 上述场景在第一次调用错误或第二次调用错误时失败。错误可以在.subscribe 的回调中或在character$.pipe (catchError) 的最后捕获。如果第一个导致错误,则不会执行第二个调用。如果您想从第一次调用中设置一些数据,只需将其移动到单独的变量或使用.tap(data =&gt; this.data = data)(但从您的示例中您几乎完全忽略了第一次调用的响应)
【解决方案4】:

您可以尝试使用switchmapforkJoin 的解决方案,以便更轻松地进行链接和错误处理。这将有助于保持代码干净,以防链不断增长为深巢。

    this.http
      .get("/api/people/1'")
      .pipe(
        catchError((err) => {
          // handle error
        }),
        switchMap((character) => {
          return forkJoin({
            character: of(character),
            homeworld: this.http.get(character.homeworld)
          });
        })
      )
      .subscribe(({ character, homeworld }) => {
        character.homeworld = homeworld;
        this.loadedCharacter = character;
      });

编辑:场景 2

this.http
      .get("/api/people/1")
      .pipe(
        catchError((err) => {
          console.log("e1", err);
        }),
        switchMap((character) => {
          return forkJoin({
            character: of(character),
            homeworld: this.http.get(character.homeworld).pipe(
              catchError((err) => {
                console.log("e2", err);
              })
            )
          });
        })
      )
      .subscribe(({ character, homeworld }) => {
        character.homeworld = homeworld;
        this.loadedCharacter = character;
      });

您可以链接一个捕获错误或添加一个单独的函数来处理错误,而无需调用下一个 API 调用。但我建议将后端逻辑抽象为角度服务并使用此方法。这将有助于保持易于阅读的结构。

【讨论】:

  • 似乎很好,而不是使用嵌套的。谢谢,投了赞成票。
  • 您是否还可以使用这种方法添加另一种用法,如下所示?首先请求更新一条记录,然后根据返回值(例如状态码)发送另一个请求以添加另一条记录并显示消息?
  • 所以如果状态码是 200,你只想发送第二个请求?
  • 其实不是,我的意思是第一次调用添加一条记录,然后发送另一个请求以更新另一条记录。但是在此期间,我想处理每个请求的错误,如果第一个请求中出现错误,我想中断并只显示错误消息而不执行第二个请求。
猜你喜欢
  • 1970-01-01
  • 2018-02-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-30
  • 2023-03-08
  • 2013-12-05
  • 2021-07-12
相关资源
最近更新 更多