[Dart] Tìm hiểu về Streams
Streams trong Dart là một chuỗi các sự kiện bất đồng bộ. Tưởng tượng như một dòng chảy dữ liệu liên tục, nơi các “mảnh” dữ liệu (các sự kiện) được phát ra theo thời gian. Bạn có thể lắng nghe (listen) dòng chảy này và xử lý từng sự kiện khi nó xuất hiện.
Các khái niệm quan trọng về Streams:
- Dòng chảy (Stream): Bản thân dòng chảy là một đối tượng cung cấp một cách để nhận một chuỗi các sự kiện theo thời gian. Các sự kiện này có thể là bất kỳ kiểu dữ liệu nào (ví dụ: số, chuỗi, đối tượng).
- Sự kiện (Event): Một mẩu dữ liệu được phát ra bởi stream. Một stream có thể phát ra nhiều sự kiện theo thời gian.
- Người nghe (Listener/Subscriber): Một phần của code “lắng nghe” stream để nhận và xử lý các sự kiện khi chúng được phát ra. Bạn đăng ký để lắng nghe một stream bằng cách sử dụng phương thức listen().
- Hoàn thành (Done): Một stream có thể kết thúc việc phát ra sự kiện, được biểu thị bằng một sự kiện “done”. Sau khi stream hoàn thành, nó sẽ không phát ra thêm sự kiện nào nữa.
- Lỗi (Error): Trong quá trình phát sự kiện, stream có thể gặp lỗi và phát ra một sự kiện lỗi. Sau khi phát ra lỗi (tùy thuộc vào loại stream), stream có thể tiếp tục phát ra các sự kiện khác hoặc hoàn thành.
Hai loại Streams chính trong Dart:
1.Single-subscription Stream:
- Chỉ có một người nghe duy nhất tại một thời điểm.
- Các sự kiện được phát ra theo đúng thứ tự và chỉ được nhận bởi người nghe hiện tại.
- Thường được sử dụng cho các nguồn sự kiện mà bạn chỉ muốn xử lý một lần, ví dụ: đọc một file lớn, nhận một phản hồi HTTP duy nhất.
- Bạn thường tạo single-subscription stream bằng cách sử dụng các hàm như File.lines(), HttpClientResponse.listen().
2.Broadcast Stream:
- Cho phép nhiều người nghe cùng một lúc.
- Mỗi người nghe sẽ nhận được tất cả các sự kiện được phát ra sau khi họ bắt đầu lắng nghe.
- Các sự kiện có thể bị bỏ lỡ nếu một người nghe tham gia sau khi sự kiện đã được phát ra.
- Thường được sử dụng cho các nguồn sự kiện mà nhiều phần của ứng dụng có thể quan tâm, ví dụ: sự kiện chuột, sự kiện mạng socket, các stream từ StreamController.broadcast().
Cách làm việc với Streams:
1.Tạo Stream: Bạn có thể tạo stream bằng nhiều cách:
Sử dụng các lớp và hàm có sẵn của Dart (ví dụ: File.lines(), Stream.fromIterable(), Stream.periodic()).
Sử dụng StreamController để tạo stream một cách thủ công và kiểm soát việc phát ra sự kiện.
2. Lắng nghe Stream: Sử dụng phương thức listen() trên đối tượng stream. Phương thức listen() nhận tối đa bốn hàm callback:
onData: Hàm này được gọi mỗi khi stream phát ra một sự kiện dữ liệu.
onError: Hàm này được gọi khi stream phát ra một sự kiện lỗi.
onDone: Hàm này được gọi khi stream hoàn thành.
cancelOnError: Một tham số boolean (mặc định là false). Nếu true, việc lắng nghe sẽ tự động bị hủy khi một lỗi xảy ra.
3. Xử lý sự kiện: Bên trong các hàm callback của listen(), bạn viết logic để xử lý dữ liệu, lỗi hoặc khi stream hoàn thành.
Ví dụ đơn giản về Single-subscription Stream:
import 'dart:async';
void main() {
// Tạo một single-subscription stream phát ra các số từ 1 đến 3
final myStream = Stream<int>.fromIterable([1, 2, 3]);
print("Bắt đầu lắng nghe stream...");
myStream.listen(
(data) {
print("Nhận dữ liệu: $data");
},
onError: (error) {
print("Lỗi xảy ra: $error");
},
onDone: () {
print("Stream đã hoàn thành!");
},
);
print("Kết thúc hàm main.");
}
// Output:
// Bắt đầu lắng nghe stream...
// Nhận dữ liệu: 1
// Nhận dữ liệu: 2
// Nhận dữ liệu: 3
// Stream đã hoàn thành!
// Kết thúc hàm main.
DartVí dụ đơn giản về Broadcast Stream:
import 'dart:async';
void main() {
// Tạo một StreamController có thể phát broadcast stream
final controller = StreamController<String>.broadcast();
final stream = controller.stream;
// Người nghe 1
stream.listen((data) {
print("Người nghe 1 nhận: $data");
});
// Người nghe 2 (tham gia sau một chút)
Future.delayed(Duration(milliseconds: 500), () {
stream.listen((data) {
print("Người nghe 2 nhận: $data");
});
});
// Phát các sự kiện
controller.sink.add("Hello");
controller.sink.add("Dart");
controller.sink.add("Streams");
// Đóng controller khi không cần nữa
controller.close();
}
// Output (thứ tự có thể khác nhau):
// Người nghe 1 nhận: Hello
// Người nghe 1 nhận: Dart
// Người nghe 1 nhận: Streams
// Người nghe 2 nhận: Dart
// Người nghe 2 nhận: Streams
DartỨng dụng của Streams:
Streams rất hữu ích trong các tình huống xử lý dữ liệu bất đồng bộ, chẳng hạn như:
- Đọc và xử lý dữ liệu từ file theo từng dòng.
- Xử lý dữ liệu nhận được từ mạng (ví dụ: WebSockets).
- Phản ứng với các sự kiện người dùng (ví dụ: click chuột, di chuyển chuột).
- Xây dựng các ứng dụng reactive, nơi các phần của ứng dụng tự động cập nhật khi có dữ liệu mới.
Tóm lại, Streams trong Dart cung cấp một cách mạnh mẽ và linh hoạt để làm việc với các chuỗi sự kiện bất đồng bộ, cho phép bạn xây dựng các ứng dụng phản ứng nhanh và hiệu quả với các nguồn dữ liệu thay đổi theo thời gian.