【问题标题】:Processing async results with Arrow-kt and Kotlin使用 Arrow-kt 和 Kotlin 处理异步结果
【发布时间】:2021-06-08 06:20:04
【问题描述】:

我有两个异步函数调用外部系统返回 Either 并且需要组合它们的结果。作为 Arrow-Kt 函数式编程的初学者,我想知道哪种方法是完成这项任务的最佳方式。 下面是我目前正在使用的代码。它确实有效,但并不真正“感觉”最直接。我正在寻找一种更“实用”的风格来获得结果。 注意:成功 List 结果的前期使用是必要的。

suspend fun getAs(): Either<Exception, List<A>> = TODO()
suspend fun getBs(): Either<Exception, List<B>> = TODO()
suspend fun doSomethingWithA(listA: List<A>): Unit = TODO()

launch {
    val deferredA = async { getAs() }
    val deferredB = async { getBs() }

    either<Exception, List<A>> {
        val listOfAs = deferredA.await()
            .bimap(leftOperation = { e ->
                println("special message on error for A")
                e
            }, rightOperation = { listA ->
                doSomethingWithA(listA)
                listA
            })
            .bind()
        val listOfBs = deferredB.await().bind()

        listOfAs.filter { it.someId !in listOfBs.map { it.someProperty } }
    }
    .map { /* handle result */ }
    .handleError { /* handle error */ }

}

另一种选择是像这样使用map{} 函数

launch {
    val deferredA = async { getAs() }
    val deferredB = async { getBs() }

    deferredA.await()
        .bimap(leftOperation = { e ->
            println("special message on error for A")
            e
        }, rightOperation = { listA ->
            doSomethingWithA(listA)
            deferredB.await().map { listB ->
                listA.filter { a -> a.someId !in listB.map { it.someProperty } }
            }
        })
        .map { /* handle result */ }
        .handleError { /* handle error */ }
}

【问题讨论】:

    标签: kotlin async-await either arrow-kt


    【解决方案1】:

    最简单的方法是将either { }parZip 结合起来。 either { } 允许您从Either&lt;E, A&gt; 中提取AparZip 是一个实用函数,用于并行运行suspend 函数。

    suspend fun getAs(): Either<Exception, List<A>> = TODO()
    suspend fun getBs(): Either<Exception, List<B>> = TODO()
    suspend fun doSomethingWithA(listA: List<A>): Unit = TODO()
    
    either {
      val list = parZip(
        {
           getAs()
             .mapLeft { e -> println("special message on error for A"); e }
             .bind()
        },
        { getBs().bind() },
        { aas, bbs ->
          aas.filter { a -> a.someId !in bbs.map { it.someProperty }
        }
      )
    
      /* Work with list and return value to `either { } */
    }.handleError { /* handle error */ }
    

    这里bind()Either&lt;E, A&gt; 中提取A。我们在parZip 内部执行此操作,这样每当遇到Left 时,它都会使either { } 块短路,这样做还会取消parZip 中仍在运行的任务。

    这样,如果getAs() 立即返回Left,则它成为either { } 的输出值,getBs() 被取消。

    【讨论】:

      【解决方案2】:

      我正要发布一个非常相似的答案。请注意,getAsgetBs 并不是真正的顺序,因为 getBs 不需要执行 getAs 的结果。他们只碰巧需要最终组合结果。换句话说:我们可以并行化

      在 Simon 建议的基础上,您还有一些我想做的事情。 (我将在此示例中将 A 和 B 替换为 NetworkUserDbUser 以尝试赋予它一些语义,否则过滤器上的那些“id”属性将不起作用。

      捕获错误并将它们映射到每个有效函数的强类型域错误。

      这将有助于减轻程序其余部分的负担,并在此基础上提供更安全的域错误层次结构,我们可以在需要时对其进行详尽的评估。

      suspend fun <A> getUsersFromNetwork(): Either<DomainError, List<NetworkUser>> =
       Either.catch { fetchUsers() }
         .mapLeft { exception ->
           println("special message on error for A")
           exception.toDomain()
         }
      

      让 doSomething 函数返回 Either,以防万一它也可能失败。

      这是一个你说它在初始 get 之后需要的函数,这意味着 flatMap 或 bind(它们是等效的)。如果我们将其提升为Either,这将确保错误短路按预期发生,因此此操作永远不会在最初没有成功的情况下运行。

      我建议这样做,因为我怀疑您在此处进行的此操作也是您的代码中第一次操作的结果,可能是将第一次操作的结果存储在本地缓存中或其他类型的影响只是在消耗这个结果。

      suspend fun doSomethingWithNetworkUsers(listA: List<NetworkUser>): Either<DomainError, Unit> = TODO()
      

      所以我们从组合函数中依赖的函数可以如下所示:

      suspend fun getUsersFromNetwork(): Either<DomainError, List<NetworkUser>> = TODO()
      suspend fun getUsersFromDb(): Either<DomainError, List<DbUser>> = TODO()
      suspend fun doSomethingWithNetworkUsers(listA: List<NetworkUser>): Either<DomainError, Unit> = TODO()
      

      还有程序:

      fun CoroutineScope.program() {
        launch {
          either {
            parZip(
              {
                val networkUsers = getUsersFromNetwork().bind()
                doSomethingWithNetworkUsers(networkUsers).bind()
                networkUsers
              },
              { getUsersFromDb().bind() }
            ) { networkUsers, dbUsers ->
              networkUsers.filter { networkUser ->
                networkUser.id !in dbUsers.map { dbUser -> dbUser.id }
              }
            }
          }
          .map { /* do something with the overall result */ }
          .handleError { /* can recover from errors here */ }
          // Alternatively:
          // .fold(ifLeft = {}, ifRight = {}) for handling both sides.
        }
      }
      

      通过将第一个操作作为组合操作执行,首先使用绑定,例如从上面的操作中提取的以下 sn-p,我们确保两个操作都在 parZip lambda 组合结果发生之前完成。

      val networkUsers = getUsersFromNetwork().bind()
      doSomethingWithNetworkUsers(networkUsers).bind()
      networkUsers
      

      【讨论】:

      • 从 Either 的左侧(异常)我真的只使用(在这种情况下)异常消息。所以它确实可以转换成一个字符串——创建一个域错误类型对于这个特定的用例来说有点矫枉过正。 DoSomethingWithA() 可能有副作用并失败,因此返回类型 Either 是个好主意,失败不能导致短路 B 的评估。副作用是 UI 的更新,我没有首先提到。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-06
      • 1970-01-01
      • 1970-01-01
      • 2019-07-17
      • 2011-08-14
      相关资源
      最近更新 更多