You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将Stripe自定义回调转为Kotlin协程解决Flow emit报错

错误原因

flow构建器本身对执行上下文有严格约束:emit方法仅允许在构建器自身的协程上下文内顺序调用,不允许在其他线程/异步回调上下文中跨上下文调用。
你代码里的PaymentIntentCallback是Stripe SDK在内部工作线程触发的,和Flow的执行上下文完全隔离,直接在回调里调用emit既不符合Flow的上下文规则,也存在线程安全问题,IDE才会提示错误,强行运行也会抛出上下文不一致的异常。

修复实现

对于这种单次返回结果的回调式API,标准协程实现分两步:先通过suspendCancellableCoroutine把回调API转成挂起函数,再在Flow中顺序调用、发射结果,全程不会有上下文冲突。
完整可运行代码:

// 1. 先把Stripe回调包装为支持取消的挂起函数,可单独复用
private suspend fun fetchPaymentIntent(clientSecret: String): PaymentIntent =
    suspendCancellableCoroutine { continuation ->
        Terminal.getInstance().retrievePaymentIntent(clientSecret,
            object : PaymentIntentCallback {
                override fun onFailure(e: TerminalException) {
                    // 协程仍活跃时才恢复,避免取消后抛异常
                    if (continuation.isActive) {
                        continuation.resumeWith(Result.failure(e))
                    }
                }

                override fun onSuccess(paymentIntent: PaymentIntent) {
                    if (continuation.isActive) {
                        continuation.resumeWith(Result.success(paymentIntent))
                    }
                }
            })
        // 协程被取消时同步清理Stripe侧请求,若SDK无取消方法可省略
        continuation.invokeOnCancellation {
            // 例如: Terminal.getInstance().cancelCurrentRequest()
        }
    }

// 2. 实现Flow接口,所有emit操作都在Flow上下文内顺序执行
override fun retrievePaymentIntent(clientSecret: String): Flow<Resource<PaymentIntent>> = flow {
    emit(Resource.Loading())
    runCatching {
        fetchPaymentIntent(clientSecret)
    }.onSuccess { intent ->
        emit(Resource.Success(intent))
    }.onFailure { e ->
        emit(Resource.Error(e as? TerminalException, e.localizedMessage ?: "获取支付信息失败"))
    }
}
其他可选方案

如果你希望直接用Flow包装回调,不需要单独抽挂起函数,可以使用专门为跨上下文发送事件设计的callbackFlow构建器,它提供的trySend方法是线程安全的,支持在任意回调线程发送值:

override fun retrievePaymentIntent(clientSecret: String): Flow<Resource<PaymentIntent>> = callbackFlow {
    // 先发送加载态
    trySend(Resource.Loading())

    val callback = object : PaymentIntentCallback {
        override fun onFailure(e: TerminalException) {
            trySend(Resource.Error(e, e.localizedMessage ?: "获取支付信息失败"))
            close() // 单次请求结束后关闭流
        }

        override fun onSuccess(paymentIntent: PaymentIntent) {
            trySend(Resource.Success(paymentIntent))
            close() // 单次请求结束后关闭流
        }
    }

    Terminal.getInstance().retrievePaymentIntent(clientSecret, callback)

    // 流被收集者取消时触发清理
    awaitClose {
        // 可选:取消Stripe侧正在执行的请求
    }
}

注意:使用callbackFlow时,在单次结果返回后必须调用close()关闭流,否则流会一直保持活跃状态,造成不必要的资源占用。

注意事项
  • 不要用GlobalScope或者其他外部协程作用域在回调里切协程再调用emit,会破坏Flow的结构化并发规则,引发内存泄漏、异常丢失、协程泄漏等问题。
  • 单次返回结果的回调优先用suspendCancellableCoroutine实现,callbackFlow更适合需要持续推送多个值的回调场景(例如实时位置监听、蓝牙设备扫描回调、长连接消息推送等)。
  • 恢复协程前必须判断isActive状态,避免协程已经被取消后调用resume抛出异常。

内容的提问来源于stack exchange,提问作者matghazaryan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 03:45:33