使用服务工作者正确地以角度方式超时 HTTP 请求
Posted
技术标签:
【中文标题】使用服务工作者正确地以角度方式超时 HTTP 请求【英文标题】:Timeout an HTTTP request properly in angular with a service worker 【发布时间】:2022-01-21 09:31:39 【问题描述】:作为我的团队正在添加的一项新功能的一部分,有人问我,当特定的 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 成功(有问题的那个)
【问题讨论】:
我无法对您的缩进做出正面或反面。很确定您对管道的调用没有右括号?此外,您的最终 catchError 结束时没有
或 return
语句。在那之后,你有一个悬空的return
和关闭的
,它们可能都应该属于catchError
。这会解析吗?
@MrkSef 嘿,我添加了右括号并将代码格式化为 JS。对不起,这是我第一次发布这样的代码,所以我尽力了。感谢您指出这一点:)
现在看起来不错??
【参考方案1】:
问题
稍微简化后,我是这样理解你目前实现的逻辑的:
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)
需要一个取消订阅方法,该方法可以取消飞行中甚至最近完成的请求(由于异步内容可能会自行排序)。
你需要看看那里。
... 或
另一种可能的解决方案
永远不要取消进行中的请求。服务器拥有唯一的事实来源,始终假设 saveCar
可能仍然成功,如果您没有被告知 upstream
(服务器)它已经失败。
在 y 秒后停止重试。你会得到另一个 504 Timeout,你可以直接把它扔给消费者来处理,不管他们怎么决定(大概和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
))
);
)
【讨论】:
我注意到第二个代码块有问题。首先,if 条件需要有 If error 是 timeout 的逻辑,并且 timeout 还没有过去我们要继续。使用当前代码,如果其中任何一个为真,它将继续。其次,这段代码的目的不同——如果发送的第一个请求需要很多时间,它就不会重试,因为我没有任何超时。主要问题是服务工作者在超时时的行为(因为请求本身正在被取消)。也许将超时逻辑移到服务器端会更聪明? 是的,在这种情况下很好。另外,是的,第二个解决方案不希望让服务器在飞行中回滚更改。如果这是您想要的,您需要在服务器中实现和/或创建一个相当复杂的 savecar 服务(使用某种握手)。我没有想到任何替代方案。 我们目前决定听从您的建议,并使用没有超时的逻辑,以防止我们在保存时存在当前泄漏。谢谢! :)以上是关于使用服务工作者正确地以角度方式超时 HTTP 请求的主要内容,如果未能解决你的问题,请参考以下文章