代码之家  ›  专栏  ›  技术社区  ›  davioooh

反应堆异常抛出的正确方式

  •  5
  • davioooh  · 技术社区  · 6 年前

    我是新来的项目 Reactor 以及一般的反应式编程。

    我目前正在研究一段与此类似的代码:

    Mono.just(userId)
        .map(repo::findById)
        .map(user-> {
            if(user == null){
                throw new UserNotFoundException();
            }
            return user;
        })
        // ... other mappings
    

    这个例子可能很愚蠢,而且确实有更好的方法来实现这个案例,但重点是:

    用A是错的吗 throw new 中的异常 map 阻止还是我应该用 return Mono.error(new UserNotFoundException()) ?

    这两种做法有什么实际区别吗?

    1 回复  |  直到 5 年前
        1
  •  9
  •   Roman M. Oleh Dokuka    6 年前

    有几种方法可以被视为异常抛出的一种方便方法:

    处理元素时使用 Flux/Mono.handle

    可以简化对可能导致错误或空流的元素的处理的方法之一是运算符 handle .

    下面的代码显示了我们如何使用它来解决我们的问题:

    Mono.just(userId)
        .map(repo::findById)
        .handle((user, sink) -> {
            if(!isValid(user)){
                sink.error(new InvalidUserException());
            } else if (isSendable(user))
                sink.next(user);
            }
            else {
                //just ignore element
            }
        })
    

    如我们所见, .handle 操作员要求通过 BiConsumer<T, SynchronousSink<> 以便处理元素。这里我们需要在我们的biconsumer中输入参数。第一个是上游的一个元素,第二个是 SynchronousSink 这有助于我们同步向下游供应元素。这种技术扩展了提供元素处理不同结果的能力。例如,如果元素无效,我们可以提供相同的错误 SycnchronousSync 取消上游生产 onError 向下游发出信号。反过来,我们可以使用相同的 手柄 操作员。一旦把手 BiConsumer 在执行过程中,没有提供任何元素,反应堆会将其视为一种过滤,并会为我们请求一个额外的元素。最后,如果元素有效,我们可以简单地调用 SynchronousSink#next 并向下游传播元素或在其上应用一些映射,因此我们将 手柄 作为 map 这里是接线员。此外,我们可以在不影响性能的情况下安全地使用该运算符,并提供复杂的元素验证,如元素验证或发送到下游的错误。

    投掷使用 #concatMap + Mono.error

    在映射期间引发异常的选项之一是替换 地图 具有 concatMap . 本质上, 连接图 差不多一样吗 flatMap 做。唯一的区别是 浓度图 一次只允许一个子流。这种行为大大简化了内部实现,并且不会影响性能。因此,我们可以使用以下代码以更实用的方式抛出异常:

    Mono.just(userId)
        .map(repo::findById)
        .concatMap(user-> {
            if(!isValid(user)){
                return Mono.error(new InvalidUserException());
            }
            return Mono.just(user);
        })
    

    在上面的示例中,如果用户无效,我们将使用返回异常 单项误差 . 我们可以用同样的方法处理通量 Flux.error :

    Flux.just(userId1, userId2, userId3)
        .map(repo::findById)
        .concatMap(user-> {
            if(!isValid(user)){
                return Flux.error(new InvalidUserException());
            }
            return Mono.just(user);
        })
    

    注释 ,在这两种情况下,我们都会返回 寒冷的 只有一个元素的流。在反应器中,有两个优化可以在回流是冷的情况下提高性能。 标量 流。因此,建议使用Flux/Mono 连接图 + .just , empty , error 因此,当我们需要更复杂的映射时,最终可能会 return null throw new ... .

    注意!永远不要检查可空性上的传入元素。反应堆项目将永远不会发送 null 因为这个暴力反应流规范(参见 Rule 2.13 ) 因此,如果 repo.findById 返回空值,反应器将为您抛出NPE异常。

    等等,为什么 连接图 比…好 平版地图 是吗?

    本质上, 平版地图 用于合并一次执行的多个子流中的元素。这意味着flatmap下面应该有异步流,因此它们可能在多个线程上处理数据,或者可能是多个网络调用。随后,这种期望对实现有很大影响,因此 平版地图 应该能够处理来自多个流的数据( Thread s)(表示并发数据结构的使用),如果从另一个流中排出,则将元素排队(表示为 Queue 对于每个子流),不要违反反应流规范规则(意味着真正复杂的实现)。把所有这些事实和我们取代一个 地图 以更方便的方式使用 Flux/Mono.error (这不会改变执行的同步性)导致了这样一个事实,即我们不需要如此复杂的运算符,我们可以使用更简单的 连接图 它是为一次异步处理一个流而设计的,为了处理标量的冷流,它有两个优化。

    使用引发异常 switchOnEmpty

    因此,当结果为空时引发异常的另一种方法是 转换空 操作员。下面的代码演示了如何使用这种方法:

    Mono.just(userId)
        .flatMap(repo::findById)
        .switchIfEmpty(Mono.error(new UserNotFoundExeception()))
    

    我们可以看到,在这种情况下 repo::findById 应该有 Mono 属于 User 作为返回类型。因此,在案例A中 用户 找不到实例,结果流将为空。因此,反应堆将调用另一种方法 单声道 ,指定为 switchIfEmpty 参数。

    按原样抛出异常

    它可以被视为一个不太可读的代码或糟糕的实践,但您可以按原样抛出异常。此模式违反了反应流规范,但反应器将为您捕获抛出的异常并将其作为 出错 向下游发出信号

    外卖

    1. 使用 把手 提供复杂元素处理的运算符
    2. 使用 连接图 + 单项误差 当我们需要在映射期间抛出异常时,这种技术最适合于异步元素处理的情况。
    3. 使用 平版地图 + 单项误差 当我们已经 平版地图 到位
    4. Null 因为返回类型是禁止的,所以 无效的 在你的下游 地图 你会得到意想不到的 出错 具有 NullPointerException
    5. 使用 开关空 在所有情况下,如果调用某个特定函数的结果完成了 空的 流动