kotlin flow操作符详解

408 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第19天,点击查看活动详情

1.流是冷的

当流调用collect时才会执行流中的任务。

2.超时取消

withTimeoutOrNull(250) { // 在 250 毫秒后超时,flow会被取消
    flow
}

3.流的构建

  ArrayList<Int>().asFlow()
  HashSet<String>().asFlow()
  (1..3).asFlow()

    flow{ 
      emit(1) 
      emit(2)
      emit("3")
   }

  flowOf(1,2,3)

4.transform

类似map,可以发射多个数据。

(1..3).asFlow()
    .transform{ request ->
        emit("make request $request ")
        emit("make request again $request ")
    }
    .collect {
        println(it)
    }

collect将会收集到6个数据。

5.限长操作符 take

fun numbers(): Flow<Int> = flow {
    try {
        emit(1)
        emit(2)
        println("This line will not execute")
        emit(3)
    } finally {
        println("Finally in numbers")
    }
}

fun main() = runBlocking<Unit> {
    numbers()
        .take(2) // 只获取前两个(数据源发射两条数据后将不再执行之后的代码,会执行finally)
        .collect { value -> println(value) }
}   

6.末端流操作符

toList/toSet

val sum = (1..5).asFlow()
    .map { it * it } // 数字 1 至 5 的平方
    .toList()

first

val first = (1..5).asFlow()
    .map { it * it } // 数字 1 至 5 的平方
    .first()

直接返回流的第一个值。

single

等待只有一个数据的流发射的数据,如果是空流或者多个数据的流,则抛出错误。

reduce/fold


fun main() = runBlocking<Unit> {
    val sum = (1..5).asFlow()
        .map { it * it } // 数字 1 至 5 的平方
        .reduce { a, b -> a + b } // 求和(末端操作符)
        // result = 55

    /* 拥有一个初始值   
    .fold(5, { a, b ->a + b })
    */
    // result = 60
}


7.buffer

上流发送数据很慢,下游处理的也很慢的时候。 可以使用buffer缓存发射项。

fun simple(): Flow<Int> = flow {
    for (i in 1..3) {
        delay(100) // 假装我们异步等待了 100 毫秒
        emit(i) // 发射下一个值
    }
}

fun main() = runBlocking<Unit> {
    val time = measureTimeMillis {
        simple()
            .buffer()
            .collect { value ->
            delay(300) // 假装我们花费 300 毫秒来处理它
            println(value)
        }
    }
    println("Collected in $time ms")
}

8.conflate

val time = measureTimeMillis {
    simple()
        .conflate() // 合并发射项,不对每个值进行处理
        .collect { value -> 
            delay(300) // 假装我们花费 300 毫秒来处理它
            println(value) 
        } 
}   
println("Collected in $time ms")

下游正在处理数据时,上游不会发送数据,直接跳过中间数据,只发送最新的值。 应用场景:数据发送的很频繁,不必处理每一个数据。

9.collectLast

val time = measureTimeMillis {
    simple()
        .collectLatest { value -> // 取消并重新发射最后一个值
            println("Collecting $value") 
            delay(300) // 假装我们花费 300 毫秒来处理它
            println("Done $value") 
        } 
}   
println("Collected in $time ms")

发送新值的时候会取消collectLast里代码的执行。
应用场景:只需要处理最新值的时候。

10.组合多个流

Zip


val nums = (1..3).asFlow() // 数字 1..3
val strs = flowOf("one", "two", "three") // 字符串
nums.zip(strs) { a, b -> "$a -> $b" } // 组合单个字符串
    .collect { println(it) } // 收集并打印

按顺序一一组合,最后数据的个数取最短的数据源。
Zip会等待两个源的值,当两个源都有数据时才发送。

Combine


val nums = (1..3).asFlow().onEach { delay(300) } // 发射数字 1..3,间隔 300 毫秒
val strs = flowOf("one", "two", "three").onEach { delay(400) } // 每 400 毫秒发射一次字符串
val startTime = System.currentTimeMillis() // 记录开始的时间
nums.combine(strs) { a, b -> "$a -> $b" } // 使用“combine”组合单个字符串
    .collect { value -> // 收集并打印
        println("$value at ${System.currentTimeMillis() - startTime} ms from start") 
    } 

Combine会组合多次,当其中任一数据源有新值得时候都会组合并发送一次。

11.展开流

flatMapConcat

将上游的flow转换成另一个flow。

流还是按照顺序执行的。

flatMapMerge

val startTime = System.currentTimeMillis() // 记录开始时间
(1..3).asFlow().onEach { delay(100) } // 每 100 毫秒发射一个数字
    .flatMapMerge {
        flow{
            emit("$it: First")
            delay(3000) // 等待 500 毫秒
            emit("$it: Second")
        }
    }
    .collect { value -> // 收集并打印
        println("$value at ${System.currentTimeMillis() - startTime} ms from start")
    }

上流会按顺序并发执行flatMapMerge中的转换flow。

flatMapLast

上流会按顺序并发执行flatMapLast中的转换flow,当有新的数据源来临时会取消之前的数据执行。

12.流异常

check

fun main() = runBlocking<Unit> {
    try {
        simple().collect { value ->
            println(value)
            check(value <= 1) { "Collected $value" }
        }
    } catch (e: Throwable) {
        println("Caught $e")
    }
} 

满足check条件则会抛出错误。

catch

fun simple(): Flow<String> = flow {
    for (i in 1..3) {
        println("Emitting $i")
        emit("$i") // 发射下一个值
        error("sd")
    }
}

fun main() = runBlocking<Unit> {
    simple()
        .catch { e -> emit("Caught $e") } // 发射一个异常
        .collect { value -> println(value) }
}

error用于发送一个错误。
catch用于捕获上流中的错误(仅上流),即不能捕获末端操作符中的异常。

我们可以将对数据的操作放在onEach中,然后下游调用catch。

simple()
    .onEach { value ->
        check(value <= 1) { "Collected $value" }                 
        println(value) 
    }
    .catch { e -> println("Caught $e") }
    .collect()

onCompletion

  • 它在流完成收集时调用。
  • onCompletion 的主要优点是其 lambda 表达式的可空参数 Throwable 可以用于确定流收集是正常完成还是有异常发生。
  • 当末端操作符(collect)中抛出了错误,onCompletion也能收到。

simple()
    .onCompletion {e->
        if (e!=null){
            println("失败")
        }
    }
    .catch { e -> emit("Caught $e") } // 发射一个异常
    .collect { value -> println(value) }

13.启动流

launchIn

  • 可指定流在哪一个协程中去完成。launchIn(Scope)将流与scope生命周期绑定,工作方式就像addEventListener 一样。 而且,这不需要相应的 removeEventListener。

  • launchIn 也会返回一个Job,可以取消该流的收集。

cancel()

fun foo(): Flow<Int> = flow { 
    for (i in 1..5) {
        println("Emitting $i") 
        emit(i) 
    }
}

fun main() = runBlocking<Unit> {
    foo().collect { value -> 
        if (value == 3) cancel()  
        println(value)
    } 
}

可以取消流的发送。流取消发送时会抛出JobCancellationException。