Stream 顧名思義就是流,簡單理解,其實就是一個異步數據隊列而已。我們知道隊列的特點是先進先出的,Stream也正是如此。
Stream 分爲兩種,單訂閱流(single subscription) 和 廣播流(broadcast)。
單訂閱流
單訂閱流的特點是隻允許存在一個監聽器,即使該監聽器被取消後,也不允許再次註冊監聽器。
如何創建Stream
Stream.periodic
periodic可以創建一個 按一定間隔,重複發出事件的流。
import 'dart:async';
void main() {
// test();
testPeriodic();
}
testPeriodic() async {
// 使用 periodic 創建流,第一個參數爲間隔時間,第二個參數爲回調函數,這個回調函數參數是整型(0開始遞增)
Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), callback);
// await for循環從流中讀取
await for (var i in stream) {
print(i);
}
}
int callback(int computationCount) {
// print("value $value");
return computationCount;
}
打印結果
0
1
2
3
4
…
每一秒生成一次自然數列,callback函數用於對生成的整數(computationCount,從0開始遞增)進行處理,處理後再放入Stream中。這裏並未處理,直接返回了。要注意,這個流是無限的。當然也可以約束條件使之停止。在後面會介紹如何給流設置條件。
約束條件
take 和 takeWhile
Stream take(int count) 用於 約束Stream中的元素數量,當流發出 count 個數據事件時,事件將會結束,stream is done。
testPeriodic() async {
Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), callback);
stream = stream.take(5);// 當放入5個元素後,監聽會停止,Stream會關閉
// await for循環從流中讀取
await for (var i in stream) {
print(i);
}
}
打印結果:
0
1
2
3
4
Stream takeWhile(bool test(T element)) 與 take 類似,它是參數是一個返回值爲bool的函數,函數功能是對當前元素進行判斷,不滿足條件則取消監聽。
Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), callback);
stream = stream.takeWhile((i) {
return i <= 5;
}
skip 和 skipWhile
表示從Stream中跳過前count個元素
testPeriodic() async {
Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), callback);
stream = stream.takeWhile((i) {
return i <= 5;
});
stream = stream.skip(3);//表示從Stream中跳過前兩個元素
// await for循環從流中讀取
await for (var i in stream) {
print(i);
}
}
skipWhile(bool test(T element)) 方法與takeWhile用法是相同的,傳入一個函數對結果進行判斷,表示跳過滿足條件的。
有幾個注意事項
- 該方法只是從Stream中獲取元素時跳過,被跳過的元素依然是被執行了的,所耗費的時間依然存在,其實只是跳過了執行完的結果而已。
- 如果該流發出的數據事件小於count,完成之前 stream將不會發射任何事件。
Stream.fromFuture
Stream.fromFuture(Future.delayed(Duration(seconds: 2), () {
return "Hello";
})).listen((onData) {
print(onData);
}, onError: (e) {
print("onError $e");
}, onDone: () {
print("onDone");
}, cancelOnError: false);//cancelOnError 爲true時,出現onError時,onDone將不會回調
打印結果
Hello
onDone
該方法從一個Future創建Stream,當Future執行完成時,就會放入Stream中,而後從Stream中將任務完成的結果取出。
監聽Stream的方法:
監聽一個流最常見的方法就是listen。當有事件發出時,流將會通知listener。Listen方法提供了這幾種觸發事件:
- onData(必填):收到數據時觸發
- onError:收到Error時觸發
- onDone:結束時觸發
- cancelOnError:遇到第一個Error時是否取消訂閱,默認爲false。cancelOnError 爲true時,出現onError時,onDone將不會回調
順便提一下:
獲取Stream結果
Stream.fromFutures
可以接收多個異步操作的結果(成功或失敗)。 也就是說,在執行異步任務時,可以通過多次觸發成功或失敗事件來傳遞結果數據或錯誤異常。 Stream 常用於會多次讀取數據的異步任務場景,如網絡內容下載、文件讀寫等.
Stream.fromFutures([
// 1秒後返回結果
Future.delayed(new Duration(seconds: 1), () {
return "hello";
}),
// 拋出一個異常
Future.delayed(new Duration(seconds: 2), () {
throw AssertionError("Error");
}),
// 3秒後返回結果
Future.delayed(new Duration(seconds: 3), () {
return 520;
})
]).listen((data) {
print(data);
}, onError: (e) {
print(e.message);
}, onDone: () {
print("onDone");
});
打印結果
hello
Error
520
onDone
Stream.fromIterable
該方法從一個集合創建Stream
List<int> list = [1, 2, 3];
Stream.fromIterable(list).listen((data) {
print(data);
}, onError: (e) {
print(e.message);
}, onDone: () {
print("onDone");
},cancelOnError: false);
打印結果
1
2
3
onDone
Stream.value
Dart2.5 新增的方法,用於從單個值創建Stream。
Stream stream = Stream.value("Hello");
// await for循環從流中讀取
stream.forEach((result){
print(result);
});
StreamController
StreamController 工具類 就如同一個管道,在這個管道中封裝了一個 Stream ,並向我們提供了兩個接口來操作 Stream 。分別是:
- sink 從Stream中的一端插入數據
- stream 從Stream的另一外彈出數據
//任意類型的流,也可定義泛型
StreamController controller = StreamController();
// 監聽這個流的出口,當有data流出時,打印這個data
controller.stream.listen((data) => print(data),
onError: (e) => print(e),
onDone: () => print("onDone"),
cancelOnError: false);
controller.add(123);
controller.add("hello");
controller.sink.add('world');
controller.addError("this is error");
controller.add(false);
controller.close();// 調用close方法,結束Stream中的邏輯處理,釋放資源,如果不調用close方法,將不會觸發onDone回調。
打印結果
123
hello
world
this is error
false
onDone
監聽Stream隊列
細心的你可能看到,上述代碼用過多種方式監聽Stream,用過四種方式,await for循環,這也是官方推薦的方式,看起來更簡潔友好,是使用forEach方法或listen方法。最後一種方式是 將Stream中所有數據存儲在List中,然後遍歷list,代碼如下:
testList() async {
Stream stream = Stream.fromIterable([1, 2, 3]);
List list = await stream.toList();
for (var i in list) {
print(i);
}
}
廣播流
在普通的單訂閱流中調用兩次listen會報錯
Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), (computationCount)=>computationCount);
stream.listen(print);
stream.listen(print);
將會報異常
Unhandled exception:
Bad state: Stream has already been listened to.
廣播流則可以允許多個監聽器存在,就如同廣播一樣,凡是監聽了廣播流,每個監聽器都能獲取到數據。
要注意,如果在觸發事件時將監聽者正添加到廣播流,當前正在觸發的事件和之前的時間,監聽着都不會收到。如果取消監聽,監聽者會立即停止接收事件。
創建廣播流方式
一種直接從Stream創建,另一種使用StreamController創建
從Stream創建
// 調用 Stream 的 asBroadcastStream 方法創建
Stream<int> stream = Stream<int>.fromIterable([1, 2, 3])
.asBroadcastStream();
stream.listen((i)=>print("listener1:$i"));
stream.listen((i)=>print("listener2:$i"));
打印結果
listener1:1
listener2:1
listener1:2
listener2:2
listener1:3
listener2:3
從StreamController創建
// 創建廣播流
StreamController streamController = StreamController.broadcast();
Stream stream = streamController.stream;
//設置監聽
stream.listen((i) => print("listener1:$i"));
stream.listen((i) => print("listener2:$i"));
//添加事件
streamController.add("event1");
streamController.add("event2");
打印結果
listener1:event1
listener2:event1
listener1:event2
listener2:event2
數據裝換
StreamTransformer
stream通過 數據轉換方法 transform,傳入參數StreamTransformer(可以把StreamTransformer當做一個數據轉換的策略),返回一個新的Stream。
通俗的講:就是把一個 Stream 作爲輸入,然後經過計算或數據轉換,輸出爲另一個 Stream。另一個 Stream 中的數據類型可以不同於原類型,數據多少也可以不同。
我們寫一個例子,將隊列裏的小寫字母轉換爲大寫字母
StreamController<String> controller = StreamController<String>();
// 創建 StreamTransformer對象
StreamTransformer streamTransformer = StreamTransformer<String, String>.fromHandlers(
handleData: (String data, EventSink sink) {
// 操作數據後,轉換爲 double 類型
sink.add(data.toUpperCase());
},
handleError: (error, stacktrace, sink) {
sink.addError('wrong: $error');
},
handleDone: (sink) {
sink.close();
},
);
// 調用流的transform方法,傳入轉換對象
Stream<String> stream = controller.stream.transform(streamTransformer);
stream.listen(print,onDone: ()=>print("onDone"));
// 添加數據,這裏的類型是int
controller.add('a');
controller.add('b');
controller.add('c');
// 調用後,觸發handleDone回調
controller.close();
輸出結果
A
B
C
onDone
總結 Stream 和 Future
Stream 和 Future 是 Dart 異步處理的核心 API。
Future 表示稍後獲得的一個數據,所有異步的操作的返回值都用 Future 來表示。但是 Future 只能表示一次異步獲得的數據。而 Stream 表示多次異步獲得的數據。比如界面上的按鈕可能會被用戶點擊多次,所以按鈕上的點擊事件(onClick)就是一個 Stream 。
簡單地說,Future將返回一個值,而Stream將返回多次值。