angular 服务工作者的 HTTP 请求超时

Timeout an HTTTP request properly in angular with a service worker

作为我的团队正在添加的新功能的一部分,我被要求在特定的 HTTP Post 请求失败时(当互联网速度慢或没有可用时),我将每 X 秒重试一次请求在 Y 秒的总跨度期间。例如,在 8 秒的时间段内每 1 秒一次。 这是我得出的代码:

    return this.carService.saveCar(car)
      .pipe(this.isRetryable() ? retryWhen(errors => {
        return errors.pipe(mergeMap(response => {
          if (response.status === TIMEOUT_EXCEPTION_CODE || response.status === UNKNOWN_EXCEPTION_CODE) {
            return of(response).pipe(delay(this.saveCarRetryTimeout)) // X seconds
          }
          
          return throwError(response);
      }));
    }) : tap(),
    this.isRetryable() ? timeout(this.saveCarProccessTime) : tap(), // Y Seconds
    tap((carId: number) => {
      this.logger.info(saveCarBaseLog.setStatus(LogStatus.COMPLETE));
    }),
    catchError((err) => {
      this.logger.error(saveCarBaseLog).setStatus(LogStatus.FAILED)); 
      return throwError(err);
    }));

isRetryable() 函数只是检查我们是否同时设置了 X 和 Y 配置,因此它不会影响进程。

这样做并看到它在本地和开发环境中都运行良好后,我们上传了版本。第二天我们遇到了一个问题 - 在 preprod 和 prod 环境中,一些汽车被保存了两次。 经过我的调查,这个问题似乎来自我们的服务人员——每当发生完全超时时,请求本身就会超时,尽管与之关联的 FETCH 请求永远不会被取消,这会在互联网刚好出现时出现问题慢(FETCH 请求最终成功,但我们没有得到任何相关指示)。 我真的不知道在这里做什么,所以欢迎任何帮助!

我不能上传网络截图,因为它是私有网络, 但在 chrome 的网络部分中,它看起来像这样:

POST 请求 - saveCar - XHR - 504 超时
POST 请求 - (ServiceWorker) saveCar - FETCH - 504 超时
POST 请求 - saveCar - XHR - 504 超时
POST 请求 - (ServiceWorker) saveCar - FETCH - 504 超时
POST 请求 - saveCar - XHR - 504 超时
POST 请求 - (ServiceWorker) saveCar - FETCH - 200 成功(有问题的那个)

问题

稍微简化后,我是这样理解你到目前为止实现的逻辑的:

class arbitraryClass{
  
  /* ... arbitrary code ... */

  arbitraryMethod(){
    return this.carService.saveCar(car).pipe(
      this.retryTimeoutLogic(),
      tap({
        complete: () => this.logger.info(saveCarBaseLog.setStatus(LogStatus.COMPLETE)),
        error: err => this.logger.error(saveCarBaseLog.setStatus(LogStatus.FAILED))
      })
    );
  }

  retryTimeoutLogic<T>(): MonoTypeOperatorFunction<T> {
    return s => !this.isRetryable() ? s : s.pipe(
      retryWhen(errors => errors.pipe(
        filter(response => {
          if (response.status !== TIMEOUT_EXCEPTION_CODE || 
              response.status !== UNKNOWN_EXCEPTION_CODE
          ) {
            throw response;
          }
          return true;
          
        }),
        delay(this.saveCarRetryTimeout) // retry after X seconds
      )),
      timeout(this.saveCarProccessTime) // error after Y Seconds
    );
  }
}

问题是当 timeout(this.saveCarProccessTime) 抛出错误时会发生什么。它在源上调用 unsubscribe 然后发出错误 downstream.

这意味着 this.carService.saveCar(car) 需要一个取消订阅方法,可以取消中途甚至最近完成的请求(由于异步内容可能会自行排序)。

你需要看看那里。

... 或

另一种可能的解决方案

切勿取消飞行中的请求。服务器拥有唯一的真实来源,如果您没有被告知 upstream(服务器)失败,请始终假设 saveCar 可能仍会成功。

y 秒后停止重试。您将获得另一个 504 超时,您可以将其直接扔给消费者以处理他们的决定(大概与 this.isRetryable() 返回 false 时他们的处理方式相同)。

retryTimeoutLogic<T>(): MonoTypeOperatorFunction<T> {
  return s => !this.isRetryable() ? s : defer(() => {

    // The time when we stop retrying on TIMEOUT_EXCEPTION_CODE &
    // UNKNOWN_EXCEPTION_CODE. After this time we rethrow instead
    const timeoutDate = new Date().getMilliseconds() +  this.saveCarProccessTime
    
    return s.pipe(
      retryWhen(errors => errors.pipe(
        filter(response => {
          if (response.status !== TIMEOUT_EXCEPTION_CODE || 
              response.status !== UNKNOWN_EXCEPTION_CODE ||
              new Date().getMilliseconds() > timeoutDate
          ) {
            throw response;
          }
          return true;
        }),
        delay(this.saveCarRetryTimeout) // retry after X seconds
      ))
    );
  })
}