在审查一些用 kotlin 编写的代码时,有件事引起了我的注意。我在一些项目中查看领域层,在一些项目中,我看到暂停功能和 Flow 一起使用,在一些项目中,我看到只使用 Flow。
例如一起暂停和流动:
class FetchMovieDetailFlowUseCase @Inject constructor(
private val repository: MovieRepository
) : FlowUseCase<FetchMovieDetailFlowUseCase.Params, State<MovieDetailUiModel>>() {
data class Params(val id: Long)
override suspend fun execute(params: Params): Flow<State<MovieDetailUiModel>> =
repository.fetchMovieDetailFlow(params.id)
}
只是流动
class GetCoinUseCase @Inject constructor(
private val repository: CoinRepository
){
operator fun invoke(coinId:String): Flow<Resource<CoinDetail>> = flow {
try {
emit(Resource.Loading())
emit(Resource.Success(coin))
}catch (e:HttpException){
emit(Resource.Error(e.localizedMessage ?: "An unexpected error occured"))
}catch (e:IOException){
emit(Resource.Error("Couldn't reach server. Check your internet connection."))
}
}
}
暂停
class GetLatestNewsWithAuthorsUseCase(
private val newsRepository: NewsRepository,
private val authorsRepository: AuthorsRepository,
private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
suspend operator fun invoke(): List<ArticleWithAuthor> =
withContext(defaultDispatcher) {
val news = newsRepository.fetchLatestNews()
val result: MutableList<ArticleWithAuthor> = mutableListOf()
// This is not parallelized, the use case is linearly slow.
for (article in news) {
// The repository exposes suspend functions
val author = authorsRepository.getAuthor(article.authorId)
result.add(ArticleWithAuthor(article, author))
}
result
}
}
这三个都是不同的项目,不要纠结于代码,这些只是我遇到的项目,我分享是为了展示例子,但我想在这里引起你注意的是,有时只有使用suspend函数,有时只使用Flow,有时两者都用。这是什么原因?你能详细解释一下吗? 我正在努力将其纳入我的逻辑
之所以有的项目只用suspend功能,有的只用Flow,有的两者都用,主要是因为项目的用例和需求不同
suspend 函数用于返回单个结果的异步操作。它们用于暂停协程的执行,直到操作完成,然后返回结果。此类操作的示例包括网络调用、磁盘 I/O 或数据库查询。
另一方面,Flow 用于随时间返回多个值的异步操作。它是一个可以发出多个值的反应流,并且可以随时取消流。此类操作的示例包括收听数据库或网络提要的实时更新。
在某些情况下,只需要一个结果,那么使用挂起函数就足够了。例如,从数据库或网络调用中获取单个项目时。在这种情况下,使用 Flow 就有点矫枉过正了。
在其他情况下,期望有多个结果,或者结果随时间更新时,Flow更合适。例如,在获取数据库或网络提要的实时更新时。在这种情况下,使用 suspend 是不够的,因为它只返回一个结果。
在某些情况下,suspend和Flow可以一起使用。例如,在获取数据库的实时更新时,您可能会使用挂起函数来获取初始数据,然后使用 Flow 来接收实时更新。另一个例子是当你需要在一个序列中执行多个异步操作时,你可以使用挂起函数将它们链接在一起,然后使用 Flow 发出结果。
总而言之,选择使用挂起函数、Flow,还是两者兼而有之,取决于具体的用例和项目的要求。
使用suspend的例子:
suspend fun getWeatherData(city: String): WeatherData {
val response = weatherApi.getWeatherData(city)
return response.body() ?: throw Exception("Failed to fetch weather data")
}
在此示例中,
getWeatherData
函数使用 suspend 进行网络调用以获取给定城市的天气数据。该函数在返回数据之前等待响应返回。
使用Flow的例子:
fun getNewsArticles(): Flow<List<Article>> = flow {
val response = newsApi.getArticles()
val articles = response.articles
emit(articles)
}
在此示例中,
getNewsArticles
函数返回新闻文章的Flow。该函数使用 flow 创建可以异步收集的数据流。该函数进行网络调用以获取新闻文章并在数据可用时立即发出数据。然后,调用者可以收集随时间发出的数据。
suspend
函数对于以暂停、同步的方式检索单个项目是有意义的。
当您决定开始收集时,流程会随着时间的推移返回多个项目。它们通常用于监视随时间变化的事物,以检索事物的最新值。例如,每当数据库中的某些内容发生变化时,来自数据库的流可以发出一个新值。
使用检索 Flow 的挂起函数将两者结合起来几乎没有意义。这是因为 Flow 本身是一个轻量级对象,通常不需要任何前期、耗时的工作来检索和创建。
在返回流时避免制作函数
suspend
的原因是这样使用起来比较麻烦。它会阻止你在协程之外使用这个漂亮的模式:
someUseCase.retrieveSomeFlow()
.onEach { ... }
.launchIn(someScope)
如果
retrieveSomeFlow()
是一个挂起函数,你必须这样做:
someScope.launch {
someUseCase.retrieveSomeFlow().collect {
...
}
}
在某些情况下,您可能只想传递流而不收集它,并且您不想为了获取流而必须启动协程。
如果您必须检索某些东西以用作作为流基础的键,则可能是一个例外。例如,从 API 检索用户 ID,然后返回使用该用户 ID 为流获取项目的流。但即使这样通常也是不必要的,因为您可以将该 ID 检索放入流程中。例如:
fun getUserItems(): Flow<UserItem> = flow {
val userId = someAPI.retrieveUserId() // calling a suspend function
emitAll(someAPI.getUserItemsFlow(userId))
}
唯一的缺点是,如果您多次收集流,则每次收集开始时都必须重新检索用户 ID。