You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular中RxJS实现HTTP返回204时重复含新ID请求流程的问题

RxJS实现基于响应状态码的条件重试(带ID更新)

问题描述

需求

  • 使用已保存的ID发送停止请求;
  • 生成新ID并发送启动请求,同时保存该新ID;
  • 若响应状态码为200则正常结束,若为204则从第一步开始重试(重试时必须用新ID)。

实现约束

尽量避免多层subscribe,仅在末尾使用一次subscribe,其余逻辑通过RxJS的pipe操作完成。

当前代码

服务端方法:

public doStuff(): Observable<string> {
  if (this.id) {
    return this.httpClient.get(`${STOP_URL}/${this.id}`).pipe(
      tap(() => {
        this.id = Date.now();
      }),
      mergeMap(() => this.httpClient.get(`${START_URL}/${this.id}`))
    );
  } else {/* 基本逻辑相同,只是无需发送停止请求 */}
}

组件调用代码:

this.service
  .doStuff()
  .pipe(
    // TODO:当响应为204时,使用新ID重复流程
    mergeMap(result => this.httpClient.get(/* 使用响应部分内容的请求 */))
).subscribe(foo => bar())

遇到的问题

无法在TODO位置实现以下逻辑:
(a) 识别响应状态码;
(b) 重试时生成新ID(至少实现(a))。
现有障碍:retryWhen仅对非2XX错误生效,repeat无法添加条件判断,repeatWhen无法获取响应内容。

解决方案

核心思路是用expand操作符——它能基于当前流的输出决定是否继续发射新的Observable,刚好适配这种需要根据响应内容判断重试的场景,同时把ID更新和状态码判断逻辑封装到流程内部。

步骤1:重构doStuff方法,返回包含状态码的完整响应

首先修改HTTP请求配置,让它返回完整的响应对象(包含状态码),而不是仅返回响应体:

import { of, Observable } from 'rxjs';
import { tap, mergeMap, map } from 'rxjs/operators';

public doStuff(): Observable<{ data: string; status: number }> {
  // 无ID时跳过停止请求,模拟一个成功响应
  const stopRequest$ = this.id 
    ? this.httpClient.get(`${STOP_URL}/${this.id}`, { observe: 'response' })
    : of({ status: 200 });

  return stopRequest$.pipe(
    tap(() => {
      this.id = Date.now(); // 生成新ID并保存
    }),
    // 发送启动请求,同样获取完整响应
    mergeMap(() => this.httpClient.get(`${START_URL}/${this.id}`, { observe: 'response' })),
    // 整理成包含数据和状态码的对象
    map(response => ({
      data: response.body as string,
      status: response.status
    }))
  );
}

步骤2:在组件中用expand实现条件重试

利用expand判断响应状态码,决定是否重试;同时用filter只保留最终成功的响应:

import { EMPTY } from 'rxjs';
import { expand, filter, mergeMap } from 'rxjs/operators';

this.service
  .doStuff()
  .pipe(
    // 条件重试:状态码为204时重新执行doStuff,否则终止流
    expand(({ status }) => status === 204 ? this.service.doStuff() : EMPTY),
    // 只保留状态码200的成功响应
    filter(({ status }) => status === 200),
    // 用成功响应的数据发起后续请求
    mergeMap(({ data }) => this.httpClient.get(/* 使用data的请求路径 */))
  )
  .subscribe(foo => bar());

方案优势

  • 完全符合“仅末尾一次subscribe”的要求,所有逻辑通过pipe串联;
  • expand可以直接访问响应状态码,解决了repeatWhen无法获取响应信息的问题;
  • 每次重试时doStuff内部都会自动生成新ID,满足重试用新ID的需求;
  • 逻辑清晰,没有多层嵌套,易于维护。

内容的提问来源于stack exchange,提问作者Panossa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 21:25:21