【问题标题】:Angular: have side effects and return Observable from a methodAngular:有副作用并从方法返回 Observable
【发布时间】:2018-01-17 03:11:45
【问题描述】:

我有这个功能:

  /**
   * Attempt login with provided credentials
   * @returns {Observable<number>} the status code of HTTP response
   */
  login(username: string, password: string): Observable<number> {
    let body = new URLSearchParams();
    body.append('grant_type', 'password');
    body.append('username', username);
    body.append('password', password);

    return this.http.post(Constants.LOGIN_URL, body, null)
      .do(res => {
        if (res.status == 200) {
          this.authTokenService.setAuthToken(res.json().auth_token);
        }
      })
      .map(res => res.status)
      .catch(err => { return Observable.throw(err.status) });
  }

此函数尝试执行登录并返回Observable&lt;number&gt;,调用者可以使用该Observable&lt;number&gt; 来通知响应的HTTP 代码。

这似乎可行,但我有一个问题:如果调用者只是调用函数而不订阅返回的Observable,则不会调用do 函数并且不会保存接收到的身份验证令牌。

do 状态的文档:

注意:这与订阅 Observable 不同。如果 do 返回的 Observable 未订阅,副作用 观察者指定的将永远不会发生。因此只是做间谍 在现有执行中,它不会触发执行 订阅即可。

我想要实现的是,无论login() 的调用者是否订阅,都会产生这种副作用。我该怎么办?

【问题讨论】:

  • 这个方法返回一个 observable。如果您不打算订阅它的响应,那么调用它有什么意义? (如果您不订阅,您将不会提出此请求)
  • @echonax,此方法正在使用中。可能有多个客户——其中一些会订阅,而另一些则不会。我不希望服务的功能依赖于外部使用的语义。
  • 你可以在login()方法中调用subscribe()

标签: angular typescript rxjs


【解决方案1】:

正如@Maximus 所解释的,按照设计,冷 Observable(如 http 调用)在您订阅它们之前不会发出任何数据。所以你的.do() 回调永远不会被调用。

另一方面,Hot Observables 会发出数据而不关心是否有订阅者。

您可以使用publish() 运算符将您的冷Observable 转换为ConnectableObservable。该 Observable 将在其 connect() 方法被调用时开始发射。

login(username: string, password: string): Observable < number > {
  let body = new URLSearchParams();
  body.append('grant_type', 'password');
  body.append('username', username);
  body.append('password', password);
  let request = this.http.post(Constants.LOGIN_URL, body, null)
    .do(res => {
      if (res.status == 200) {
        this.authTokenService.setAuthToken(res.json().auth_token);
      }
    })
    .map(res => res.status)
    .catch(err => {
      return Observable.throw(err.status)
    }).publish();
  request.connect();
  // type assertion because nobody needs to know it is a ConnectableObservable
  return request as Observable < number > ; 
}

正如@Maximus 所说。如果订阅发生在 ajax 调用完成后,您将不会收到结果通知。要处理这种情况,您可以使用publishReplay(1) 而不是简单的publish()PublishReplay(n) 将向新订阅者重复源 Observable 发出的最后 n 个元素。

【讨论】:

  • 每次登录调用都会有一个新的请求
  • 这不是 OP 的问题。 (顺便说一句:您的解决方案也是如此。)
  • 不,我的解决方案不会每次都发送请求。这就是为什么我使用变量sent
  • 还请记住,一旦http.get() 完成,publish().connect() 的所有订阅 login() 都不会收到通知
  • @n00dl3,这似乎可行,但我有一个问题。 authTokenService.setAuthToken() 总是用 undefined 参数调用。看起来 res.json().auth_token 无法解析我收到的实际数据。你碰巧知道问题是什么吗?
【解决方案2】:

在我下面的回答中,我假设您已经习惯了 Promises 在 AngularJS 中 HTTP 的早期版本中提供的行为:

 login(username: string, password: string): Observable<number> {
    return this.http.post(Constants.LOGIN_URL, body, null)
      .then(res => {
        if (res.status == 200) {
          this.authTokenService.setAuthToken(res.json().auth_token);
        }
      })
      .then(res => res.status)
      .catch(err => { return Observable.throw(err.status) });

this.http.get() 调用返回的 observable 是 cold observable。这意味着除非有人订阅它,否则它不会开始做任何事情。这是设计使然。由于没有订阅,因此链接到返回的 observable 的所有运算符都不会做任何事情。

您需要订阅才能发出请求,然后与未来的订阅者分享结果。我认为AsyncSubject 是一个很好的候选人:

  sent = false;
  s = new AsyncSubject();

  login(username: string, password: string): Observable<number> {   
    if (!this.sent) {
      this.http.post(Constants.LOGIN_URL, body, null)
        .do(res => {
          if (res.status == 200) {
            this.authTokenService.setAuthToken(res.json().auth_token);
          }
        })
        .map(res => res.status)
        .catch(err => { return Observable.throw(err.status) })
        .subscribe(s);

      this.sent = true;
    }

    return s;
  }

这样只会进行一次http调用,并且包括do在内的所有运算符都只会运行一次。之后,返回的结果会缓存在AsyncSubject 中,并传递给所有未来的订阅者。

【讨论】:

  • 您不需要主题。再加上你的代码是错误的。设置为false 的常量如何变为true
  • 不知道为什么要投反对票,这确实解决了他的问题。尽管我仍然难以理解问题所在。
  • @n00dl3,我怎么不需要主题?如果可以,为什么要多次调用身份验证?您也可能意识到我没有完全按照应有的方式放置代码。 我写了伪代码,错别字是预料之中的事情
  • @PierreDuc,我相信 OP 之前使用了 Promise,如果你写 this.http.get().then(do)... 会立即发出请求,而无需订阅 then。这是hot 行为,这就是我认为OP 对现有http.get() 的期望
  • 您可以使用publish()connect() 进行转换。当方法被调用时它总是false,因为它不是一个类属性,而是一个简单的变量。每当你调用那个方法时,sent 就是false,那么保留它有什么意义呢?
猜你喜欢
  • 2021-09-26
  • 1970-01-01
  • 2021-09-24
  • 2023-01-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多