【问题标题】:RxJS: Check token validity before calling S3 List, and wait for the token in case it is invalidRxJS:在调用 S3 List 之前检查令牌的有效性,并等待令牌以防它无效
【发布时间】:2020-08-19 15:41:02
【问题描述】:

请多多包涵,因为这个问题又长又详细,而且我对 RxJS 还很陌生。

我正在尝试在 Angular 中创建类似于 Windows Explorer 的 Amazon S3 浏览器。

Something like this...

左侧列表将包含所有根文件夹(它不会是树形视图),当单击任何根文件夹时,其中的子文件夹和文件将显示在右侧的详细信息视图中。

我需要为左侧列表中的每个根文件夹创建一个新的 S3 访问令牌。我有一个这样做的后端服务。此令牌在特定时间段内有效。所以当前token无效的情况有:-

  1. 如果用户单击左侧列表中的某个其他根文件夹。
  2. 如果令牌到期。

这是我为管理此令牌到期条件而编写的内容:-

private accessTokenSource: BehaviorSubject<AccessToken | null> = new BehaviorSubject(null);
accessToken$ = this.accessTokenSource.asObservable();

getAccessToken() {
    return this.http.get(${this.accessTokenEndpoint}).pipe(
        tap((accessToken) => {
            // set Access token in a subject
            this.accessTokenSource.next(accessToken);
        }),
        switchMapTo(timer(55*60*1000).pipe(
            tap(() => {
                // reset access token in subject since now token is invalid - Expiry case
                this.accessTokenSource.next(null);
            })
        ))
    );
}

// Whoever subscribes to this will fetch the token and start the expiration timer

由于不想在视图层中公开访问令牌获取逻辑,我的每个左列表和详细信息组件都调用了 s3Service 中的方法 getDetails(currentPrefix: string)。该方法首先检查令牌的有效性是否能够调用 S3 API,然后调用listObjects 操作并返回结果。这是我到目前为止所拥有的:-

// Checks the validity of token for the current prefix
checkAccessTokenValidity(currentPrefix: string) {
    let isTokenValid: boolean = true;

    // This uses access token set in the subject
    // According to me, it will be reset by the timer's tap operation (when it expires)
    const sub = this.accessToken$.subscribe((token) => {
        // Check token's expiry or usability for current folder and update isTokenValid accordingly
        if(!token || !currentPrefix.includes(token.rootFolder)) {
            isTokenValid = false;
        }
    });
    sub.unsubscribe();

    return isTokenValid;
}


// Public method to call from list and details components
getDetails(currentPrefix: string) {
    const isTokenValid = this.checkAccessTokenValidity(currentPrefix);
    if(!isTokenValid) {
        // This will fetch the token and start the TIMER
        this.getAccessToken().subscribe(() => {});
    }
    // I think that this will not work, since if getAccessToken takes time,
    // then accessToken$ will still be invalid!
    const objectList$ = this.accessToken$.pipe(
        map(token => {
            // S3 List Objects method here, with current token
        })
    )
}

如何解决检查令牌有效性然后等待我的服务返回新的有效令牌以调用 S3 API 的问题?任何帮助将非常感激。这种方法也可能是完全错误的,所以也请随时纠正我。

【问题讨论】:

    标签: javascript angular amazon-s3 rxjs


    【解决方案1】:

    我想说没有必要仅仅为了获取BehaviorSubject 的当前值而创建另一个订阅。

    这意味着这些行:

    const sub = this.accessToken$.subscribe((token) => {
          if(!token || !currentPrefix.includes(token.rootFolder)) {
            isTokenValid = false;
          }
        });
    sub.unsubscribe();
    

    可以替换为

    const isTokenValid = !!this.accessTokenSource.value;
    

    正如您已经提到的,getAccessToken 需要一些时间,这意味着您无法同步获得其结果。

    一个快速的解决办法是这样的:

    const tokenValid$ = of(this.accessTokenSource.value); 
    
    const tokenInvalid$ = merge(
      // Not interested in the values emitted as side effects are produced in `tap()`
      // With this, we're just subscribing. This way, an HTTP call will be made
      this.getAccessToken().pipe(ignoreElements()),
    
      // `accessTokenSource` is a `BehaviorSubject` and we don't want its current value,
      // that's why we're skipping it. Next time it emits, it will have the value returned from `getAccessToken`
      this.accessTokenSource.pipe(skip(1))
    );
    
    const objectList$ = iif(() => isTokenValid, tokenValid$, tokenInvalid$);
    

    iff() 用于在订阅时间决定订阅哪个 observable。

    if(() =&gt; booleanValue, subscribeToThisIfTrue, subscribeToThisIfFalse)

    这样,当您订阅 objectList$ 时,它会根据令牌是否有效获取适当的 observable。

    【讨论】:

    • 太棒了。这澄清了大部分事情 - 只是一个后续问题 - 最后的 iif(() => {...}) 是什么?编辑:-阅读文档-这完全有道理!万分感谢。我会告诉你情况如何。
    • 我会试试这个,让你知道结果如何。
    • 我添加了一些关于iif() 的解释。当然!
    • 嗨@Andrei,我尝试了这个实现,这是有道理的。然而。我遇到的问题是我收到数百个 getAccessToken() 请求(直到调用堆栈溢出),只有当我刷新需要调用 getFolderContents() 的路由上的页面时。如果我从其他地方导航到这条路线,只会发送一个请求。我怀疑 merge(...) 与它有关?
    • ‘merge’ 确保请求被发出,所以我认为没有问题。您是否在多个地方订阅了 objectList$?如果没有,您能否显示 getFolderContents 的代码?
    猜你喜欢
    • 2021-08-06
    • 2018-12-30
    • 2019-09-22
    • 2020-10-21
    • 2016-05-07
    • 2018-10-25
    • 1970-01-01
    • 1970-01-01
    • 2013-04-06
    相关资源
    最近更新 更多