首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用Arrow-kt和Kotlin处理异步结果

用Arrow-kt和Kotlin处理异步结果
EN

Stack Overflow用户
提问于 2021-06-08 06:20:04
回答 2查看 632关注 0票数 1

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

代码语言:javascript
复制
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{}函数,如下所示

代码语言:javascript
复制
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 */ }
}
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-06-08 07:10:07

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

代码语言:javascript
复制
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<E, A>中提取A。我们在parZip中这样做,这样每当一个Left遇到它时,它就会短路either { }块,通过这样做,它还会取消parZip中仍在运行的任务。

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

票数 6
EN

Stack Overflow用户

发布于 2021-06-08 07:30:53

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

在西蒙建议的基础上,我还要做一些额外的事情。(在本例中,我将用NetworkUserDbUser替换A和B,以尝试给出一些语义,因为否则过滤器上的"id“属性将无法工作。

捕获错误并将它们映射到在每个有效函数上强键入域错误。

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

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

使doSomething函数返回,以防它也会失败。

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

我建议这样做,因为我怀疑这里的这个操作也是第一个操作的结果,可能是将第一个操作的结果存储在本地缓存中的结果,或者是简单地消耗该结果的另一种效果。

代码语言:javascript
复制
suspend fun doSomethingWithNetworkUsers(listA: List<NetworkUser>): Either<DomainError, Unit> = TODO()

因此,我们将依赖于组合函数的功能如下所示:

代码语言:javascript
复制
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()

而该方案:

代码语言:javascript
复制
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.
  }
}

通过将第一个操作作为一个组合的操作,首先绑定,如下面从上面提取的代码片段中所示,我们确保这两个操作都是在parZip lambda进行组合结果之前完成的。

代码语言:javascript
复制
val networkUsers = getUsersFromNetwork().bind()
doSomethingWithNetworkUsers(networkUsers).bind()
networkUsers
票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/67882361

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档