
一、基本概念
- 错误是流终止的主动原因:例如上游逻辑抛出异常。
- 取消是流终止的被动原因:例如外部主动取消订阅。
- 优先级规则:如果错误已经发生,Reactor 会优先处理错误,不会多余地触发取消。
Maven依赖:
java
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.bluefoxyu</groupId>
<artifactId>untitled</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/io.projectreactor/reactor-core -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.8.0</version>
</dependency>
</dependencies>
</project>
二、单个 Flux 示例
1. 触发 doOnError(不会触发 doOnCancel)
java
import reactor.core.publisher.Flux;
import java.time.Duration;
public class CancelErrorDemo1 {
public static void main(String[] args) throws InterruptedException {
Flux.interval(Duration.ofMillis(100)) // 每 100ms 产生一个数字 (0, 1, 2, ...)
.doOnNext(i -> System.out.println("上游产生了: " + i))
.map(i -> {
if (i == 3) {
throw new RuntimeException("在 3 处引爆!");
}
return "处理后: " + i;
})
.doOnCancel(() -> System.out.println("!!! 上游感知到 cancel 信号,停止生产 !!!"))
.doOnError(e -> System.err.println("--- doOnError 感知到: " + e.getMessage() + " ---"))
.subscribe(
System.out::println,
error -> System.err.println("--- 最终订阅者处理错误: " + error.getMessage() + " ---")
);
Thread.sleep(2000); // 保持主线程存活
}
}
输出:

关键点:
- 错误信号(
onError)是主动终止原因:当map抛出异常时,错误信号直接传播到下游,触发doOnError和订阅者的错误回调。 - 取消信号(
cancel)不会被触发:错误终止是"计划内"的终止逻辑,无需额外触发取消。
2. 触发 doOnCancel(无错误,仅主动取消)
java
import reactor.core.publisher.Flux;
import reactor.core.Disposable;
import java.time.Duration;
public class CancelDemo {
public static void main(String[] args) throws InterruptedException {
Disposable disposable = Flux.interval(Duration.ofMillis(100))
.doOnNext(i -> System.out.println("上游产生了: " + i))
.doOnCancel(() -> System.out.println("!!! 上游感知到 cancel 信号,停止生产 !!!"))
.subscribe(i -> System.out.println("处理后: " + i));
Thread.sleep(300); // 运行一会
disposable.dispose(); // 主动取消订阅
Thread.sleep(200); // 等待 cancel 信号传播
}
}
输出:

关键点:
- 取消信号是被动终止原因:通过
Disposable.dispose()主动取消订阅时,上游会收到 cancel 信号,触发doOnCancel。 - 无错误发生,因此
doOnError不会被触发。
三、多个 Flux 在一条链上
示例 1:错误传播与取消副作用
java
import reactor.core.publisher.Flux;
import java.time.Duration;
public class MergeCancelErrorDemo1 {
public static void main(String[] args) throws InterruptedException {
Flux<Integer> flux1 = Flux.range(1, 5)
.delayElements(Duration.ofMillis(100))
.doOnNext(i -> System.out.println("[flux1 emit] " + i))
.doOnCancel(() -> System.out.println(">>> flux1 cancel"))
.doOnError(e -> System.err.println(">>> flux1 error: " + e.getMessage()));
Flux<String> flux2 = flux1.map(i -> {
if (i == 3) throw new RuntimeException("flux2 在 3 处爆了!");
return "F2-" + i;
})
.doOnCancel(() -> System.out.println(">>> flux2 cancel"))
.doOnError(e -> System.err.println(">>> flux2 error: " + e.getMessage()));
Flux<String> flux3 = flux2.map(s -> "F3(" + s + ")")
.doOnCancel(() -> System.out.println(">>> flux3 cancel"))
.doOnError(e -> System.err.println(">>> flux3 error: " + e.getMessage()));
flux3.subscribe(
data -> System.out.println("下游收到: " + data),
error -> System.err.println("=== 下游接收到错误: " + error.getMessage() + " ===")
);
Thread.sleep(2000);
}
}

关键点:
- 错误信号向上游传播:
flux2在i=3时抛出异常,触发自身的doOnError。- 错误信号继续传播到
flux1,导致flux1被取消(触发doOnCancel)。
- 取消是错误的副作用:
- 上游(
flux1)的doOnCancel是因下游错误而间接触发的,而非主动取消。
- 上游(
示例 2:merge 合并多个 Flux
java
import reactor.core.publisher.Flux;
import java.time.Duration;
public class MergeCancelErrorDemo {
public static void main(String[] args) throws InterruptedException {
// flux1:正常流,只打印 cancel 信号
Flux<String> flux1 = Flux.interval(Duration.ofMillis(100))
.map(i -> "F1-" + i)
.doOnNext(s -> System.out.println("[flux1 emit] " + s))
.doOnCancel(() -> System.out.println(">>> flux1 cancel"))
.doOnError(e -> System.err.println(">>> flux1 error: " + e.getMessage()));
// flux2:在 i==3 时抛出异常
Flux<String> flux2 = Flux.interval(Duration.ofMillis(150))
.map(i -> {
if (i == 3) throw new RuntimeException("flux2 爆了!");
return "F2-" + i;
})
.doOnNext(s -> System.out.println("[flux2 emit] " + s))
.doOnCancel(() -> System.out.println(">>> flux2 cancel"))
.doOnError(e -> System.err.println(">>> flux2 error: " + e.getMessage()));
// merge 合并两个 flux
Flux.merge(flux1, flux2)
.subscribe(
data -> System.out.println("下游收到: " + data),
error -> System.err.println("=== 下游接收到错误: " + error.getMessage() + " ===")
);
Thread.sleep(1000);
}
}

关键点:
- merge 的错误传播:
flux2抛出异常时,错误信号会终止整个合并后的流。- 所有活跃的上游(
flux1和flux2)都会收到取消信号(即使flux1本身无错误)。
- 取消信号的广播:
merge在收到错误后,会向所有输入流发送取消信号,确保资源释放。
四、总结
1. 错误 vs 取消
|-----------------|-----|------------------------------------------|
| 信号类型 | 主动性 | 特点 |
| 错误(onError) | 主动 | 主动终止信号,可能触发上游的 cancel(但不会重复触发 doOnError) |
| 取消(cancel) | 被动 | 通常由外部主动取消或错误传播引起 |
2. 操作符链中的信号传播
- 错误信号会优先向上游传播,可能导致上游触发
doOnCancel。 merge等组合操作符会广播取消信号到所有输入流。
五、通俗类比
|----------------|---------------|-----------------------------------|
| 概念 | 类比 | 含义 |
| doOnError | 像"工厂着火报警" | 立即停止生产,处理火灾(错误),无需再通知"停止生产"(取消) |
| doOnCancel | 像"接到上级通知停工" | 可能是外部原因(市场关闭),也可能是内部火灾(错误)引发的连锁反应 |
| merge 的取消 | 像"总部命令所有分厂停工" | 只要一个分厂出事,所有分厂必须停工 |