使用Arrow kt和Kotlin处理异步结果



我有两个对外部系统的异步函数调用,返回"任一<异常,Something>并且需要结合它们的结果。作为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 */ }
}

最简单的方法是将either { }parZip组合。either { }允许您从Either<E, A>中提取A,而parZip是用于并行运行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 */ }

这里CCD_ 9从CCD_ 11中提取CCD_。我们在parZip内部这样做,这样每当遇到Left时,它就会短路either { }块,这样做也会取消parZip中仍在运行的任务。

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

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

在西蒙建议的基础上,你还有一些我想做的额外事情。(我将用NetworkUserDbUser替换本例中的A和B,试图给它一些语义,因为否则过滤器上的"id"属性将不起作用

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

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

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中,将确保错误短路按预期发生,因此此操作从未在初始操作中运行过。

我建议这样做,因为我怀疑你在这里的这个操作也是第一个操作在你的代码中产生的影响,可能是将第一个操作的结果存储在本地缓存中,或者是其他类型的简单消耗结果的影响。

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.
}
}

通过将第一个操作作为一个先绑定的组合操作来执行,就像从上面的操作中提取的以下片段一样,我们确保在parZip lambda组合结果之前完成这两个操作。

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

最新更新