openplanning

Xử lý lỗi trong Dart Stream

  1. Stream.handleError()
  2. Thêm sự kiện lỗi vào Stream
  3. Stream.transform()
Đầu tiên, chúng ta hãy xem một ví dụ để biết điều gì sẽ xẩy ra khi một lỗi trên Stream không được xử lý.
ex1.dart
Stream<int> getIntStream() async* {
  for (int i = 0; i < 5; i++) {
    await Future.delayed(Duration(seconds: 2));
    if (i == 2) {
      throw Exception('My Custom Error!');
    }
    yield i;
  }
}

Future<void> main() async {
  Stream<int> myStream = getIntStream();

  await for (int value in myStream) {
    print("Value: $value");
  }
}
Value: 0
Value: 1
Unhandled exception:
Exception: My Custom Error!
#0      getIntStream (package:dart_14669_stream_error_handling/ex1.dart:5:7)
<asynchronous suspension>
#1      main (package:dart_14669_stream_error_handling/ex1.dart:14:3)
<asynchronous suspension>

1. Stream.handleError()

Phương thức handleError() của Stream tạo ra một Stream khác bao bọc lấy Stream hiện tại và xử lý các sự kiện lỗi.
Định nghĩa:
Nội dung:
handleError() **
Stream<T> handleError(
   Function onError, {
   bool test( dynamic error )?,
})
handleError() **
Stream<T> handleError(Function onError, {bool test(error)?}) {
  final void Function(Object, StackTrace) callback;
  if (onError is void Function(Object, StackTrace)) {
    callback = onError;
  } else if (onError is void Function(Object)) {
    callback = (Object error, StackTrace _) {
      onError(error);
    };
  } else {
    throw ArgumentError.value(
        onError,
        "onError",
        "Error handler must accept one Object or one Object and a StackTrace"
            " as arguments.");
  }
  return new _HandleErrorStream<T>(this, callback, test);
}
bool test(dynamic error)?
  • Đây là một tham số tuỳ chọn. Nếu tham số này không được cung cấp mọi sự kiện lỗi trên Stream đều được chuyển tới onError() để xử lý.
  • Nếu tham số này được cung cấp, sự kiện lỗi sẽ được so khớp với hàm test(error), nếu hàm này trả về true sự kiện lỗi mới được chuyển tới hàm onError().
Function onError
  • void Function(Object, StackTrace)
  • void Function(Object)
Khi lỗi được chuyển tới hàm onError() để xử lý, bạn có thể có các lựa chọn sau:
  • Ném trả lại (rethrow) lỗi này lên Stream hoặc ném một lỗi tương tự lên Stream.
  • Không làm gì cả, và coi như sự kiện lỗi này chưa từng tồn tại trên Stream.
Nếu bạn muốn chuyển đổi một sự kiện lỗi thành một sự kiện dữ liệu hãy sử dụng phương thức transform(StreamTransformer) được đề cập phía cuối bài viết này hoặc xem bài viết về StreamTransformer.
  • Dart StreamTransformer (***)
Trong ví dụ này, việc ném ra một lỗi trong hàm async* sẽ làm kết thúc Stream. Ném ra một lỗi không phải là cách để đặt một sự kiện lỗi lên Stream.
error_handling_ex1.dart
Stream<int> getIntStream() async* {
  for (int i = 0; i < 5; i++) {
    await Future.delayed(Duration(seconds: 2));
    if (i == 2) {
      throw Exception('My Custom Error!');
    }
    yield i;
  }
}

Future<void> main() async {
  Stream<int> intStream = getIntStream();
  Stream<int> wrappedStream = intStream.handleError(
    (Object error, StackTrace stackTrace) {
      print("Handle error in onError function: $error");
    },
  );
  await for (int value in wrappedStream) {
    print("Value: $value");
  }
}
Value: 0
Value: 1
Handle error in onError function: Exception: My Custom Error!

2. Thêm sự kiện lỗi vào Stream

Các ví dụ ở trên cho thấy rằng việc ném ra một lỗi trong hàm async* có thể làm kết thúc Stream. Trong phần này chúng ta sẽ thảo luận về các cách thông dụng để đặt một sự kiện lỗi lên Stream.
Về cơ bản, bạn có thể lựa chọn một trong các giải pháp sau:
  • yield* Stream.error(errorObject);
  • yield* Stream.fromFuture(Future.error(Exception());
  • yield* () async* { throw Exception(); }();
yield* Stream.error()
Sử dụng yield* Stream.error() để thêm một sự kiện lỗi vào Stream thay vì ném ra một lỗi.
ex2.dart
Stream<int> getIntStream() async* {
  for (int i = 0; i < 5; i++) {
    await Future.delayed(Duration(seconds: 2));
    if (i == 2 || i == 3) {
      yield* Stream.error('My Custom Error at index $i!');
    }
    yield i;
  }
}

Future<void> main() async {
  Stream<int> intStream = getIntStream();

  await for (int value in intStream) {
    print("Value: $value");
  }
}
Kết quả chạy ví dụ ở trên cho thấy, nếu một sự kiện lỗi trên Stream không được xử lý nó sẽ làm kết thúc Stream:
Value: 0
Value: 1
Unhandled exception:
My Custom Error at index 2!
Chúng ta sẽ sửa lại ví dụ trên một chút, sử dụng phương thức handleError() để xử lý lỗi.
error_handling_ex2.dart
Stream<int> getIntStream() async* {
  for (int i = 0; i < 5; i++) {
    await Future.delayed(Duration(seconds: 2));
    if (i == 2 || i == 3) {
      yield* Stream.error('My Custom Error at index $i!');
    }
    yield i;
  }
}

Future<void> main() async {
  Stream<int> intStream = getIntStream();
  Stream<int> wrappedStream = intStream.handleError(
    (Object error, StackTrace stackTrace) {
      print("Handle error in onError function: $error");
    },
  );
  await for (int value in wrappedStream) {
    print("Value: $value");
  }
}
Output:
Value: 0
Value: 1
Handle error in onError function: My Custom Error at index 2!
Value: 2
Handle error in onError function: My Custom Error at index 3!
Value: 3
Value: 4
Chúng ta sửa lại ví dụ trên một chút nữa để đảm bảo rằng một sự kiện hoặc là một lỗi hoặc là một dữ liệu (Không thể là cả hai).
error_handing_ex2b.dart
Stream<int> getIntStream() async* {
  for (int i = 0; i < 5; i++) {
    await Future.delayed(Duration(seconds: 2));
    if (i == 2 || i == 3) {
      yield* Stream.error('My Custom Error at index $i!');
    } else {
      yield i;
    }
  }
}

Future<void> main() async {
  Stream<int> intStream = getIntStream();
  Stream<int> wrappedStream = intStream.handleError(
    (Object error, StackTrace stackTrace) {
      print("Handle error in onError function: $error");
    },
  );
  await for (int value in wrappedStream) {
    print("Value: $value");
  }
}
Output:
Value: 0
Value: 1
Handle error in onError function: My Custom Error at index 2!
Handle error in onError function: My Custom Error at index 3!
Value: 4
yield* Stream.fromFuture(Future.error(Exception())
error_handing_ex3.dart
Stream<int> getIntStream() async* {
  for (int i = 0; i < 5; i++) {
    await Future.delayed(Duration(seconds: 2));
    if (i == 2 || i == 3) {
      yield* Stream.fromFuture(
        Future<int>.error(
          Exception("My Custom Error at index $i!"),
        ),
      );
    } else {
      yield i;
    }
  }
}

Future<void> main() async {
  Stream<int> intStream = getIntStream();
  Stream<int> wrappedStream = intStream.handleError(
    (Object error, StackTrace stackTrace) {
      print("Handle error in onError function: $error");
    },
  );
  await for (int value in wrappedStream) {
    print("Value: $value");
  }
}
Output:
Value: 0
Value: 1
Handle error in onError function: Exception: My Custom Error at index 2!
Handle error in onError function: Exception: My Custom Error at index 3!
Value: 4
yield* () async* { throw Exception(); }();
error_handing_ex4.dart
Stream<int> getIntStream() async* {
  for (int i = 0; i < 5; i++) {
    await Future.delayed(Duration(seconds: 2));
    if (i == 2 || i == 3) {
      yield* () async* {
        throw Exception("My Custom Error at index $i!");
      }() as Stream<int>;
    } else {
      yield i;
    }
  }
}

Future<void> main() async {
  Stream<int> intStream = getIntStream();
  Stream<int> wrappedStream = intStream.handleError(
    (Object error, StackTrace stackTrace) {
      print("Handle error in onError function: $error");
    },
  );
  await for (int value in wrappedStream) {
    print("Value: $value");
  }
}

3. Stream.transform()

Phương thức transform() được sử dụng để chuyển đổi Stream<T> hiện tại thành Stream<S>. Bằng cách sử dụng phương thức này bạn có thể biến đổi một sự kiện lỗi trên Stream<T> thành một sự kiện dữ liệu trên Stream<S>.
Stream<S> transform<S>(StreamTransformer<T, S> streamTransformer)
Trong ví dụ này chúng ta chuyển đổi một Stream<int> thành một Stream<String>, với các sự kiện lỗi trên Stream<int> sẽ được chuyển đổi thành một String mặc định.
transform_ex1.dart
import 'dart:async';

Stream<int> getIntStream() async* {
  for (int i = 0; i < 5; i++) {
    await Future.delayed(Duration(seconds: 2));
    if (i == 2 || i == 3) {
      yield* Stream.error('My Custom Error at index $i!');
    } else {
      yield i;
    }
  }
}

Future<void> main() async {
  // Stream<int> --> Stream<String>
  StreamTransformer<int, String> transformer =
      StreamTransformer<int, String>.fromHandlers(
    handleData: (int value, EventSink<String> es) {
      es.add("String $value");
    },
    handleError: (Object error, StackTrace st, EventSink<String> es) {
      es.add("String Value");
    },
    handleDone: (EventSink<String> es) {
      es.add("Done!");
      es.close(); // Optional
    },
  );

  Stream<int> intStream = getIntStream();
  Stream<String> wrappedStream = intStream.transform<String>(transformer);

  await for (String value in wrappedStream) {
    print(value);
  }
}
Output:
String 0
String 1
String Value
String Value
String 4
Done!
  • Dart StreamTransformer (***)

Các hướng dẫn lập trình Dart

Show More