
本文介绍如何在 project reactor 中优雅处理 mono.zip 的部分失败场景,通过 onerrorresume 封装各源并统一聚合结果,确保只要任一 mono 成功即返回有效响应,仅在全部失败时才抛出异常。
本文介绍如何在 project reactor 中优雅处理 mono.zip 的部分失败场景,通过 onerrorresume 封装各源并统一聚合结果,确保只要任一 mono 成功即返回有效响应,仅在全部失败时才抛出异常。
在响应式编程中,Mono.zip 是组合多个异步操作的常用方式,但其默认行为是“全有或全无”:一旦任意一个源(如 funA()、funB()、funC())发生错误,整个 zip 流立即终止并发射错误信号,其余仍在进行的 Mono 会被取消 —— 这显然无法满足“允许部分失败、优先返回可用结果”的业务需求。
Mono.zipDelayError() 虽能等待所有 Mono 完成后再聚合错误,但它仅将多个异常合并为 CompositeException,仍无法区分各来源的成功/失败状态,也无法提取已成功的结果。因此,真正可行的方案是在 zip 前对每个 Mono 进行错误拦截与结果封装,使其无论成功或失败,都输出一个统一的、可判别的包装类型。
✅ 核心思路:错误转义 + 类型归一化
我们定义泛型包装类 ResultWrapper
- 成功时:result 字段非空,error 为 null;
- 失败时:error 字段非空,result 为 null。
再通过 onErrorResume 将异常转化为 Mono.just(new ResultWrapper(e)),确保每个源都稳定发出一个 ResultWrapper 实例:
class ResultWrapper<t> {
private final T result;
private final Throwable error;
public ResultWrapper(T result) {
this.result = result;
this.error = null;
}
public ResultWrapper(Throwable error) {
this.result = null;
this.error = error;
}
public Optional<t> getResult() {
return Optional.ofNullable(result);
}
public Optional<throwable> getError() {
return Optional.ofNullable(error);
}
}</throwable></t></t>
? 提示:使用 Optional 替代裸 null 判断,提升类型安全与可读性。
接着,封装工具方法 wrapError,对任意 Mono
private static <t> Mono<resultwrapper>> wrapError(Mono<t> source) {
return source
.map(ResultWrapper::new)
.onErrorResume(e -> Mono.just(new ResultWrapper(e)));
}</t></resultwrapper></t>
最后,在 zip 阶段组合三个已封装的 Mono,并在 map 中按需解析结果:
Mono.zip(
wrapError(funA()),
wrapError(funB()),
wrapError(funC())
)
.map(tuple -> {
ResultWrapper<typea> a = tuple.getT1();
ResultWrapper<typeb> b = tuple.getT2();
ResultWrapper<typec> c = tuple.getT3();
// 策略示例:只要有一个成功,就构造响应;全失败则抛出 CompositeException
List<throwable> errors = new ArrayList();
List<object> successes = new ArrayList();
a.getResult().ifPresent(successes::add);
b.getResult().ifPresent(successes::add);
c.getResult().ifPresent(successes::add);
a.getError().ifPresent(errors::add);
b.getError().ifPresent(errors::add);
c.getError().ifPresent(errors::add);
if (!successes.isEmpty()) {
return buildSuccessResponse(successes); // 自定义组装逻辑
} else {
throw new CompositeException(errors); // 全失败时聚合异常
}
});</object></throwable></typec></typeb></typea>
⚠️ 注意事项与最佳实践
- 避免副作用泄露:onErrorResume 中的 Mono.just(...) 不应触发新副作用(如远程调用),否则会破坏响应式链的纯函数特性;
- 异常分类处理:生产环境中建议根据异常类型(如 TimeoutException vs BusinessException)做差异化兜底,而非一概封装;
- 资源清理:若被封装的 Mono 涉及资源(如数据库连接),需确保 onErrorResume 不阻碍 doFinally 或 usingWhen 的正常执行;
- 性能考量:zip 本身会等待所有 Mono 完成,即使某源因超时失败,也需等待其他源结束(除非显式加 timeout())。必要时可在 wrapError 前添加超时控制;
- 可测试性:ResultWrapper 应实现 equals/hashCode 并提供 Builder 或静态工厂方法,便于单元测试构造边界场景。
通过该模式,你不仅实现了“部分失败可接受”的语义,还获得了完全可控的结果编排能力 —— 无论是返回首个成功项、聚合所有成功结果,还是按权重降级 fallback,均可在 map 阶段灵活实现。这才是响应式系统中面向失败设计(Design for Failure)的正确打开方式。










