在Kotlin中进行多次异步等待

时间:2018-07-23 17:43:51

标签: android kotlin async-await coroutine kotlin-coroutines

promoType中的

obj = [字符串列表] 它更像是在这里运行10个Firebase查询,查找10个特定的节点集并进一步下降。

我不确定,是否需要对每个查询进行异步/等待,但是我只想运行其中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
    }
}

1 个答案:

答案 0 :(得分:3)

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

  1. 您的外部async(UI)没道理
  2. 您的内部async(CommonPool)也没有意义,因为您的数据库调用已异步
  3. 您在await之后async处立即使用反模式,这使其不是真正的“异步”(但请参见上文,无论有没有此内容,整个过程都是异步的)
  4. 您的提取功能具有更改用户类型的副作用
  5. 要将结果转移给调用方,请再次使用副作用代替返回值

您的代码应该简单得多。您应该声明一个suspend fun,其返回值是一对(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)。得到非空值后,请更改用户类型:

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()
            }
    }
}