问题描述
我有两个对外部系统的异步函数调用,返回任何一个
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 { }
允许您从 A
中提取 Either<E,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 */ }
此处 bind()
从 A
中提取 Either<E,A>
。我们在 parZip
中执行此操作,以便每当遇到 Left
时,它都会使 either { }
块短路,并且这样做还会取消 parZip
中仍在运行的任务。
这样,如果 getAs()
立即与 Left
一起返回,则它成为 either { }
的输出值,getBs()
被取消。
我正要发布一个非常相似的答案。请注意,getAs
和 getBs
并不是真正连续的,因为 getBs
不需要执行 getAs
的结果。他们只是碰巧最终需要合并结果。换句话说:我们可以并行化
这里,除了 Simon 的建议之外,我还有一些额外的事情要做。 (在此示例中,我将用 NetworkUser
和 DbUser
替换 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