ant*_*009 5 kotlin kotlin-coroutines
kotlin coroutines version 1.3.8
kotlin 1.3.72
Run Code Online (Sandbox Code Playgroud)
这是我第一次使用协程,并且我已经使用协程转换了我的 rxjava2。但由于这是我第一次想知道我是否遵循最佳实践。
我的一个问题是捕获异常,因为在 kotlin 中,这可能是不好的做法,因为吞下异常可能会隐藏一个严重的错误。但是使用协程还有其他方法可以捕获错误。在 RxJava 中,使用 onError 很简单。
这会让测试更容易吗?
这是挂起功能的正确使用吗?
非常感谢您的任何建议。
interface PokemonService {
@GET(EndPoints.POKEMON)
suspend fun getPokemons(): PokemonListModel
}
Run Code Online (Sandbox Code Playgroud)
如果响应太慢或某些网络错误,交互器将在 10 秒后超时
class PokemonListInteractorImp(private val pokemonService: PokemonService) : PokemonListInteractor {
override suspend fun getListOfPokemons(): PokemonListModel {
return withTimeout(10_000) {
pokemonService.getPokemons()
}
}
}
Run Code Online (Sandbox Code Playgroud)
在我的视图模型中,我使用了 viewModelScope。只是想知道我是否应该捕捉异常。
fun fetchPokemons() {
viewModelScope.launch {
try {
shouldShowLoading.value = true
pokemonListLiveData.value = pokemonListInteractor.getListOfPokemons()
}
catch(error: Exception) {
errorMessage.value = error.localizedMessage
}
finally {
shouldShowLoading.value = false
}
}
}
Run Code Online (Sandbox Code Playgroud)
在我的片段中,我只是观察实时数据并填充适配器。
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
bindings = FragmentPokemonListBinding.inflate(inflater, container, false)
setupAdapter()
pokemonViewModel.registerPokemonList().observe(viewLifecycleOwner, Observer { pokemonList ->
pokemonAdapter.populatePokemons(pokemonList.pokemonList)
})
return bindings.root
}
Run Code Online (Sandbox Code Playgroud)
我建议使用sealed Resultclass 和try/catchblock 来处理 api 响应异常:
sealed class Result<out T : Any>
class Success<out T : Any>(val data: T) : Result<T>()
class Error(val exception: Throwable, val message: String = exception.localizedMessage) : Result<Nothing>()
inline fun <T : Any> Result<T>.onSuccess(action: (T) -> Unit): Result<T> {
if (this is Success) action(data)
return this
}
inline fun <T : Any> Result<T>.onError(action: (Error) -> Unit): Result<T> {
if (this is Error) action(this)
return this
}
Run Code Online (Sandbox Code Playgroud)
在PokemonListInteractorImpusingtry/catch块中捕获异常并返回适当的Result:
class PokemonListInteractorImp(private val pokemonService: PokemonService) : PokemonListInteractor {
override suspend fun getListOfPokemons(): Result<PokemonListModel> {
return withTimeout(10_000) {
try {
Success(pokemonService.getPokemons())
} catch (e: Exception) {
Error(e)
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
在ViewModel你可以使用扩展函数onSuccess,onError在Result对象上处理结果:
fun fetchPokemons() = viewModelScope.launch {
shouldShowLoading.value = true
pokemonListInteractor.getListOfPokemons()
.onSuccess { pokemonListLiveData.value = it }
.onError { errorMessage.value = it.message }
shouldShowLoading.value = false
}
Run Code Online (Sandbox Code Playgroud)
当您使用launch协程构建器时,它会冒泡出异常。所以我认为这CoroutineExceptionHandler将是一种以更惯用的方式处理未捕获异常的替代方法。优点是
看看这个例子;我试图展示一些场景;
/**
* I have injected coroutineScope and the coroutineExceptionHandler in the constructor to make this class
* testable. You can easily mock/stub these in tests.
*/
class ExampleWithExceptionHandler(
private val coroutineScope: CoroutineScope = CoroutineScope(
Executors.newFixedThreadPool(2).asCoroutineDispatcher()
),
private val coroutineExceptionHandler: CoroutineExceptionHandler = CoroutineExceptionHandler { _, throwable ->
println(
"Exception Handler caught $throwable, ${throwable.suppressed}" //you can get the suppressed exception, if there's any.
)
}
) {
/**
* launch a coroutine with an exception handler to capture any exception thrown inside the scope.
*/
fun doWork(fail: Boolean): Job = coroutineScope.launch(coroutineExceptionHandler) {
if (fail) throw RuntimeException("an error...!")
}
}
object Runner {
@JvmStatic
fun main(args: Array<String>) {
val exampleWithExceptionHandler = ExampleWithExceptionHandler()
//a valid division, all good. coroutine completes successfully.
runBlocking {
println("I am before doWork(fail=false)")
exampleWithExceptionHandler.doWork(false).join()
println("I am after doWork(fail=false)")
}
//an invalid division. Boom, exception handler will catch it.
runBlocking {
println("I am before doWork(fail=true)")
exampleWithExceptionHandler.doWork(true).join()
println("I am after doWork(fail=true)")
}
println("I am on main")
}
}
Run Code Online (Sandbox Code Playgroud)
输出
I am before doWork(fail=false)
I am after doWork(fail=false)
I am before doWork(fail=true)
Exception Handler caught java.lang.RuntimeException: an error...!, [Ljava.lang.Throwable;@53cfcb7a
I am after doWork(fail=true)
I am on main
Run Code Online (Sandbox Code Playgroud)
可以看到异常已经被处理程序成功捕获。如果协程是嵌套的,则可以使用suppressed方法获取内部异常。
这种方法适用于非异步协程。该async协同程序是一个不同的野兽。如果您尝试在同一代码中await使用async协程runBlocking,则异常将不会像launch类型一样传播处理。它仍然会抛出范围并杀死主线程。对于异步,我看到您可以使用supervisorScope或包装协程(我没有机会使用)。
由于传播的未处理异常可以全局处理。这种风格可以帮助您重用异常处理程序代码和任何后续操作。例如,文档建议;
通常,处理程序用于记录异常、显示某种错误消息、终止和/或重新启动应用程序。
当您将 Spring 框架与全局异常处理程序一起使用时,可以找到类似的方法。
可能的缺点是;
关于暂停,如果您的 API/函数是完全异步的,您可以返回协程作用域创建的Job或Deferred<T>。否则,您必须在代码中的某处阻塞以完成协程并返回值。
这个文档非常有用https://kotlinlang.org/docs/reference/coroutines/exception-handling.html
另一个特定于 Android 应用程序的好资源 - https://alexsaveau.dev/blog/kotlin/android/2018/10/30/advanced-kotlin-coroutines-tips-and-tricks/#article
在您的 PokemonListInteractorImp 类中,处理响应异常并对其执行任何您想要的操作。在 ViewModel 中,您为列表中的某些 LiveData 对象设置值,这应该已经是成功状态。尝试类似的方法:
protected suspend fun <T> requestApiCall(call: suspend () -> T): Either<FailureState, T> {
return try {
Either.Right(call.invoke())
} catch (e: HttpException) {
return Either.Left(FailureState.ServerError)
} catch (e: UnknownHostException) {
return Either.Left(FailureState.NetworkConnection)
} catch (e: Throwable) {
e.printStackTrace()
return Either.Left(FailureState.UnknownError)
}
}
Run Code Online (Sandbox Code Playgroud)
故障状态类别:
sealed class FailureState {
object NetworkConnection : FailureState()
object ServerError : FailureState()
object UnknownError : FailureState()
/** * Extend this class for feature specific failures.*/
abstract class FeatureFailure: FailureState()
}
Run Code Online (Sandbox Code Playgroud)
ViewModel,类似:
fun loadQuestions(type: String) {
viewModelScope.launch {
questionsUseCase.invoke(type).fold(::handleError, ::handleUsersResponse)
}
}
private fun handleUsersResponse(questionsResponse: QuestionsResponse) {
questionsResponse.questions?.apply {
postScreenState(ShowQuestions(map { it.toDomainModel() }.toMutableList()))
}
}
Run Code Online (Sandbox Code Playgroud)
类似的事情,希望有帮助。但是,如果您只是想处理协程中的异常,这里是很好的来源: https://medium.com/androiddevelopers/exceptions-in-coroutines-ce8da1ec060c# :~:text=Coroutines%20use%20the%20regular%20Kotlin ,以%20不同%20种方式对待%20异常%20。
如果您有任何疑问,请尽管询问。
| 归档时间: |
|
| 查看次数: |
2345 次 |
| 最近记录: |