首页 文章

Angular:有副作用并从方法返回Observable

提问于
浏览
2

我有这个功能:

/**
   * 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<number> ,以便获得响应的HTTP代码的通知 .

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

do 州的文件:

注意:这与Observable上的订阅不同 . 如果没有订阅do返回的Observable,Observer指定的副作用将永远不会发生 . 因此,它只是简单地监视现有的执行,它不会像订阅那样触发执行 .

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

2 回答

  • 1

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

    另一方面,如果有订户,热的Observable将发出数据而不关心 .

    您可以使用 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个元素 .

  • 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 可观察的 . 这意味着除非有人订阅,否则它不会开始做任何事情 . 那是设计上的 . 您链接到返回的可观察对象的所有运算符都没有订阅 .

    您需要订阅发出请求,然后与未来的订阅者共享结果 . 我认为 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 中,并将其传递给所有未来的订阅者 .

相关问题