用Reactor抛出异常的正确方法

时间:2018-12-03 14:03:13

标签: java reactive-programming project-reactor reactor

我是项目Reactor的新手,并且通常是反应式编程。

我目前正在编写类似于以下代码:

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

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

throw new块中使用map异常是错误的,还是应该用return Mono.error(new UserNotFoundException())替换它?

这两种方式有什么实际区别吗?

1 个答案:

答案 0 :(得分:17)

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

使用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信号。反过来,我们可以使用相同的handle运算符来“过滤”。一旦执行了句柄BiConsumer并且未提供任何元素,Reactor会将其视为一种过滤,并将为我们请求其他元素。最后,如果该元素有效,我们可以简单地调用SynchronousSink#next并将其传播到下游或在其上应用一些映射,因此这里将handle作为map运算符。此外,我们可以安全地使用该运算符,而不会影响性能,并提供复杂的元素验证,例如元素验证或向下游发送错误。

使用#concatMap + Mono.error抛出

在映射过程中引发异常的选项之一是将map替换为concatMap。本质上,concatMap所做的几乎与flatMap相同。唯一的区别是concatMap一次仅允许一个子流。这种行为大大简化了内部实现,并且不影响性能。因此,我们可以使用以下代码来以更实用的方式引发异常:

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

在上面的示例中,如果用户无效,我们使用Mono.error返回异常。我们可以使用Flux.error对通量执行相同的操作:

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

注意,在两种情况下,我们都返回仅包含一个元素的 cold 流。在Reactor中,如果返回的流是冷的 scalar 流,有几个优化措施可以提高性能。因此,当需要更复杂的映射时,建议使用Flux / Mono concatMap + .justemptyerror,最终可能会导致{{ 1}}或return null

  

注意!永远不要检查传入元素的可空性。由于该反应式流规范(请参阅Rule 2.13),因此Reactor项目将永远不会为您发送throw new ...。因此,如果null返回null,Reactor将抛出NPE例外。

等等,为什么repo.findByIdconcatMap好?

本质上,flatMap旨在合并一次执行的多个子流中的元素。这意味着flatMap在其下应具有异步流,因此它们可能处理多个线程上的数据,或者可能是多个网络调用。随后,这种期望对实现产生了很大的影响,因此flatMap应该能够处理来自多个流(flatMap)的数据(意味着并发数据结构的使用),如果有其他资源的消耗,则将元素排队流(意味着为每个子流的Thread分配额外的内存),并且不违反反应式流规范规则(意味着实现起来非常复杂)。计算所有这些事实,以及我们将普通的Queue操作(是同步的)替换为使用map(不会改变执行的同步性)的更方便的引发异常的事实,会导致实际上,我们不需要这么复杂的运算符,我们可以使用更简单的Flux/Mono.error,其设计用于一次异步处理单个流,并进行了一些优化以处理标量冷流。

使用concatMap

引发异常

因此,在结果为空时引发异常的另一种方法是switchOnEmpty运算符。以下代码演示了如何使用该方法:

switchOnEmpty

我们可以看到,在这种情况下,Mono.just(userId) .flatMap(repo::findById) .switchIfEmpty(Mono.error(new UserNotFoundExeception())) 的{​​{1}}中的repo::findById应该作为返回类型。因此,如果找不到Mono实例,则结果流将为空。因此,Reactor将调用备用User,指定为User参数。

直接抛出异常

它可能被视为可读性较低的代码或不良做法,但您可以按原样抛出异常。这种模式违反了反应式流规范,但是反应器会为您捕获引发的异常并将其作为Mono信号传播到下游

外卖

  1. 使用switchIfEmpty运算符以提供复杂的元素处理
  2. 当我们需要在映射过程中引发异常时,请使用onError + .handle,但这种技术最适合异步元素处理的情况。
  3. 当我们已经安装好concatMap时,请使用Mono.error + flatMap
  4. Mono.error作为返回类型是被禁止的,因此在下游flatMap中使用Null会得到意外的null而不是map
  5. 如果调用某些特定函数的结果以 empty
  6. 结尾,则在需要发送错误信号的所有情况下都使用onError