【问题标题】:ReactiveX observable that uses another observable使用另一个可观察对象的 ReactiveX 可观察对象
【发布时间】:2016-07-10 05:47:31
【问题描述】:

我正在尝试编写一个 Angular 服务,该服务将包装常规 Http 服务并在 Bearer 令牌不可用或无效时自动进行身份验证调用。

这是通过服务进行的常规 GET 调用的示例:

this.httpWithAutomagicAuth
    .get("http://localhost:5001/books")
    .map(res => res.json())
    .subscribe(
        data => {
            self.data = data;
        }
    );

这是我对此类服务的非常草率的实现。显然,我没有在代码中使用正确的 ReactiveX 习惯用法。

你可以很容易地注意到我正在尝试构建一个使用 this.login() 返回的另一个 observable 的 observable (Observable.create)。我确信对于这种情况有更好的链接/嵌套 observable 方法。

请提出改进​​代码的建议:

  • 简洁
  • 易于阅读和理解

    @Injectable()
    export class HttpWebApiAuthService {
    
    constructor(private http: Http) { }
    
    // ...
    
    public get(url: string, options?: RequestOptionsArgs): Observable<Response> {
        // TODO this code is for test purpose only (basically, it required to enforce the jwtToken retrieval branch execution)
        // this.clearJwtToken();
    
        if (!this.getJwtToken()) {
            return Observable.create(
                (result: Observer<Response>) => {
                    this.login()
                        .map(res => res.json())
                        .subscribe(
                            data => {
                                this.saveJwtToken(data.id_token);
    
                                this.executeGet(url, options)
                                    .subscribe(authenticationResult => {
                                        result.next(authenticationResult);
                                    });
                            },
                            error => { console.error("Authentication error", error); },
                            () => { console.info("Authentication complete"); }
                        );
                    },
                    error => { console.error("OBSERVABLE error: ", error); },
                    () => { console.info("OBSERVABLE complete");
                }
            );
        } else {
            return this.executeGet(url, options);
        }
    }
    
    private login() : Observable<Response> {
        const authBody =
            {
                "client_id": "...",
                "username": "...",
                "password": "...",
                // ...
            };
        const headers = new Headers();
        headers.append("Content-Type", "application/json");
    
        return this.http.post("https://AUTH_URL", JSON.stringify(authBody), { headers: headers });
    }
    
    // ...
    
    }
    

【问题讨论】:

    标签: typescript angular reactive-programming


    【解决方案1】:

    让我们将发出单个值的 Observable 称为任务,通常是异步任务。 让我们将流程分解为更小的任务:

    首先,您已经有了返回Observable&lt;Response&gt;login() 任务。 让我们扩展它来保存 jwtToken:

    let loginAndSaveToken = login().map(res => res.json())
      .do((data: any) => this.saveJwtToken(data.id_token)/*, onError, onComplete if neccessary*/);
    

    现在继续您的get(url: string, options?: RequestOptionsArgs) 函数。如果您还没有有效的令牌,您需要先登录并保存令牌,然后继续调用 http.get,对吗?为了do this, then that,你需要concat Observables,或者使用concatMap。但如果您已经拥有有效令牌,则无需重新登录,只需返回 http.get。它等价于do A, then B,其中 A 是 dummy 任务。我正在以Observable.of(1)

    的身份执行虚拟任务
    public get(url: string, options?: RequestOptionsArgs): Observable<Response>
    {
        return (this.tokenValid() ? Observable.of(1) : loginAndSaveToken)
          .concatMap(x => this.executeGet(url, options));
    }
    

    现在您的流程更清晰了。但它可能有一些警告,例如: 如果您的第二个get 呼叫在第一个成功登录之前出现,它将尝试再次登录。如果正在进行中,最好让它等待身份验证结果,特别是如果您的身份验证服务器配置为在一个时间窗口内拒绝来自特定用户的多个请求。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-08-19
      • 2017-07-12
      • 1970-01-01
      • 1970-01-01
      • 2023-04-08
      • 1970-01-01
      相关资源
      最近更新 更多