Kotlin coroutine: exception handling of Flow

The sample code is as follows:

launch(Dispatchers.Main) {
    // 第一部分
    flow {
        emit(1)
        throw NullPointerException("e")
    }.catch {
        Log.d("liduo", "onCreate1: $it")
    }.collect {
        Log.d("liudo", "onCreate2: $it")
    }

    // 第二部分
    flow {
        emit(1)
    }.onCompletion {
        Log.d("liduo", "onCreate3: $it")
    }.collect {
        Log.d("liudo", "onCreate4: $it")
    }

    // 第三部分
    flow {
        emit(1)
        throw NullPointerException("e")
    }.retryWhen { cause, attempt ->
        cause !is NullPointerException && attempt <= 2
    }.collect {
        Log.d("liudo", "onCreate5: $it")
    }
}
复制代码

1.catch method

The catch method is used to catch the exception generated by the upstream stream, the code is as follows:

publicfun<T> Flow<T>.catch(action: suspendFlowCollector<T>.(cause: Throwable) -> Unit): Flow<T> =
    flow { // 创建Flow对象// 触发上游流的执行,并捕获异常val exception = catchImpl(this)
        // 捕获到异常,则回调action处理if (exception != null) action(exception)
    }
复制代码

The catch method is an extension method of the Flow interface and returns an object of type Flow. In the catch method, calling the flow method creates a Flow object.

The core of the catch method is to capture the exception through the catchImpl method. If the exception is successfully caught, the callback parameter action is processed. The parameter action here is an extension method of the FlowCollector interface, so you can continue to call the emit method to send values ​​downstream.

1. catchImpl method

When the collect method is called downstream, the execution of the Flow object created by the catch method will be triggered, and the catchImpl method will be called for processing. The code is as follows:

internalsuspendfun<T> Flow<T>.catchImpl(
    collector: FlowCollector<T>
): Throwable? {
    // 保存下游流执行抛出的异常var fromDownstream: Throwable? = nulltry {
        // 触发上游流的执行
        collect {
            try {
                // 将上游流发送的值作为参数,触发下游流执行
                collector.emit(it)
            } catch (e: Throwable) { // 如果下游流在执行中发生异常,保存并抛出
                fromDownstream = e
                throw e
            }
        }
    } catch (e: Throwable) { // 这里捕获的异常,可能为上游流的异常——collect方法,// 也可能为下游流的异常——emit方法// 如果异常是下游流产生的异常,或者是协程取消时抛出的异常if (e.isSameExceptionAs(fromDownstream) || e.isCancellationCause(coroutineContext)) {
            throw e // 再次抛出,交给下游处理
        } else { // 如果是上游流的异常且不为协程取消异常return e // 成功捕获
        }
    }
    // 未捕获到异常,返回returnnull
}
复制代码

The catchImpl method is an extension method of the Flow interface, so when the collect method is called, it will trigger the execution of the upstream flow. The core of the catchImpl method is to pass the value sent by the upstream to the downstream processing, and perform an exception capture operation on this process.

2. onCompletion method

The onCompletion method is used to execute the last stream after all upstream streams are executed. The code is as follows:

publicfun<T> Flow<T>.onCompletion(
    action: suspendFlowCollector<T>.(cause: Throwable?) -> Unit
): Flow<T> = unsafeFlow { // 创建一个Flow对象try {
        // 触发上游流的执行// this表示下游的FlowCollector
        collect(this)
    } catch (e: Throwable) {// 如果下游发生异常// 将异常封装成ThrowingCollector类型的FlowCollector,并回调参数action,
        ThrowingCollector(e).invokeSafely(action, e)
        // 抛出异常throw e
    }
    // 如果正常执行结束,会走到这里val sc = SafeCollector(this, currentCoroutineContext())
    try {
        // 回调执行参数action
        sc.action(null)
    } finally {
        sc.releaseIntercepted()
    }
}
复制代码

The onCompletion method is an extension method of the Flow interface, so when the collect method is called, it will trigger the execution of the upstream flow. At the same time, pass in this as a parameter, and this indicates that when the downstream flow calls the collect method, the type of Flow object passed to the unsafeFlow method is an object of FlowCollector. The core of the onCompletion method is to use the Flow object created by itself as the connection container between upstream and downstream. Only when the flow is completely executed or an exception occurs during execution, the collect method can be executed and continue to execute downward.

1.unsafeFlow method

The unsafeFlow method is used to create a type of Flow object. Compared with the SafeFlow class mentioned in Kotlin Coroutine: Basic Principles of Flow , the Flow object created by the unsafeFlow method will not check the execution context. The code is as follows:

@PublishedApiinternalinlinefun<T>unsafeFlow(@BuilderInferencecrossinline block: suspendFlowCollector<T>.() -> Unit): Flow<T> {
    // 返回一个匿名内部类returnobject : Flow<T> {
        // 回调collect方法是直接执行blockoverridesuspendfuncollect(collector: FlowCollector<T>) {
            collector.block()
        }
    }
}
复制代码

Although the onCompletion method internally uses the unsafeFlow method to create a Flow object, it uses the SafeCollector class. According to what was mentioned in Kotlin Coroutine: Fundamentals of Flow , when calling the emit method of the SafeCollector class, the context will be checked. So the actual effect is the same as using the SafeFlow class.

2. ThrowingCollector class

The ThrowingCollector class is also a FlowCollector for wrapping exceptions. When its emit method is called, a wrapped exception will be thrown, the code is as follows:

privateclassThrowingCollector(privateval e: Throwable) : FlowCollector<Any?> {
    overridesuspendfunemit(value: Any?) {
        // 抛出异常throw e
    }
}
复制代码

Why recreate the ThrowingCollector object instead of using the downstream FlowCollector object?

In order to prevent when the downstream flow execution fails, the emit method is called to send data when the action parameter of the onCompletion method is executed, which will cause the onCompletion method to be used as the method that is not the last to be executed when it is used in the "finally code block". The onCompletion method is matched with the catch method to achieve the effect of the try-catch-finally code block.

3. retryWhen method

The retryWhen method is similar to the catch method and can be used to catch exceptions generated by upstream streams. But the difference between the two is that the retryWhen method can also decide whether to trigger the execution of the upstream flow again according to the "exception type" and "retry times", and when the retryWhen method does not intend to trigger the execution of the upstream flow again, the caught An exception will be thrown, the code is as follows:

// 参数cause表示捕获到的异常// 参数attempt表示重试的次数// 参数predicate返回true表示重新触发上游流的执行publicfun<T> Flow<T>.retryWhen(predicate: suspendFlowCollector<T>.(cause: Throwable, attempt: Long) -> Boolean): Flow<T> =
    // 创建一个Flow对象
    flow {
        // 记录重试次数var attempt = 0L// 表示是否重新触发var shallRetry: Booleando {
            // 复位成false
            shallRetry = false// 触发上游流的执行,并捕获异常val cause = catchImpl(this)
            // 如果捕获到异常if (cause != null) {
                // 用户判断,是否要重新触发if (predicate(cause, attempt)) {
                    // 表示要重新触发
                    shallRetry = true// 重试次数加1
                    attempt++
                } else { // 如果用户不需要重新触发// 则抛出异常throw cause
                }
            }
        // 判断是否重新触发
        } while (shallRetry)
    }
复制代码

The retryWhen method is an extension method of the Flow interface. The core of the retryWhen method uses the catchImpl method to trigger the upstream stream and catch exceptions, and adds retry logic judged by the user.

Click the card below to get Android learning materials!

Guess you like

Origin blog.csdn.net/m0_70748845/article/details/129381638