首页 > 解决方案 > kotlin 中的多个异步等待

问题描述

obj in promoType = [list of string] 它更像是 10 个 firebase 查询在这里运行,查看 10 个特定的节点集并进一步下降。

我不确定,我是否需要在每个查询上都设置 async / await,但我想要的是运行 10 个这些查询,然后导致我的 couponKey 是否为空。我要做的就是显示输入的优惠券是否正确。

此外,在changeUserType(couponKey, couponFoundAtKey) 中,发生了一些数据库写入操作。

fun checkPromo(promoCodeET: String) = async(UI) {
    try {
        val database = PersistentFirebaseUtil.getDatabase().reference
        val job = async(CommonPool) {

            for (obj in promoType) {
                val query = database.child("promos").child(obj).orderByChild("promoCode").equalTo(promoCodeET)

                query.addListenerForSingleValueEvent(object :
                        ValueEventListener {
                    override fun onDataChange(dataSnapshot: DataSnapshot) {
                        if (dataSnapshot.exists()) {
                            couponKey = dataSnapshot.key.toString()
                            couponFoundAtKey = dataSnapshot.children.first().key.toString()
                            if (couponKey.isNotEmpty())
                                changeUserType(couponKey, couponFoundAtKey)
                            flag = true
                        }
                    }

                    override fun onCancelled(error: DatabaseError) {
                        // Failed to read value
                    }
                })
                if (flag) break
            }
        }
        job.await()            

    }
    catch (e: Exception) {
    }
    finally {
        if (couponKey.isEmpty()){
            Toast.makeText(this@Coupon, "Invalid coupon", Toast.LENGTH_LONG).show()
        }
        flag = true
    }
}

标签: androidkotlinasync-awaitcoroutinekotlin-coroutines

解决方案


我发现您的代码有几处错误:

  1. 你有一个async(UI)没有意义的外在
  2. 你的内部async(CommonPool)也没有意义,因为你的数据库调用已经是异步的
  3. 您在紧随await其后的地方使用反模式async,使其不是真正的“异步”(但见上文,无论有没有这个,整个事情都是异步的)
  4. 您的获取功能具有更改用户类型的副作用
  5. 要将结果传输给调用者,您再次使用副作用而不是返回值

您的代码应该更简单。您应该声明 a suspend fun,其返回值为 pair (couponKey, coupon)

suspend fun fetchPromo(promoType: String, promoCodeET: String): Pair<String, String>? =
    suspendCancellableCoroutine { cont ->
        val database = PersistentFirebaseUtil.getDatabase().reference
        val query = database.child("promos").child(promoType)
                .orderByChild("promoCode").equalTo(promoCodeET)
        query.addListenerForSingleValueEvent(object : ValueEventListener {
            override fun onDataChange(dataSnapshot: DataSnapshot) {
                cont.resume(
                    dataSnapshot
                        .takeIf { it.exists() }
                        ?.let { snapshot ->
                            snapshot.key.toString()
                                .takeIf { it.isNotEmpty() }
                                ?.let { key ->
                                    Pair(key, snapshot.children.first().key.toString())
                                }
                        }
                )
            }

            override fun onCancelled(error: DatabaseError?) {
                if (error != null) {
                    cont.resumeWithException(MyException(error))
                } else {
                    cont.cancel()
                }
            }
        })
    }

要调用此函数,launch(UI)请在调用站点使用 a。获得非空值后更改用户类型:

launch(UI) {
    var found = false
    for (type in promoType) {
        val (couponKey, coupon) = fetchPromo(type, "promo-code-et") ?: continue
        found = true
        withContext(CommonPool) {
            changeUserType(couponKey, coupon)
        }
        break
    }
    if (!found) {
        Toast.makeText(this@Coupon, "Invalid coupon", Toast.LENGTH_LONG).show()
    }
}

你说它changeUserType执行一些数据库操作,所以我将它们包装在一个withContext(CommonPool).

另请注意,我在函数外部提取了促销类型的循环。这将导致查询按顺序执行,但您可以编写不同的调用代码来实现并行查找:

var numDone = 0
var found = false
promoType.forEach { type ->
    launch(UI) {
        fetchPromo(type, "promo-code-et")
            .also { numDone++ }
            ?.also { (couponKey, coupon) ->
                found = true
                launch(CommonPool) {
                    changeUserType(couponKey, coupon)
                }
            }
            ?: if (numDone == promoType.size && !found) {
                Toast.makeText(this@Coupon, "Invalid coupon", Toast.LENGTH_LONG).show()
            }
    }
}

推荐阅读