Skip to content
This repository was archived by the owner on Oct 17, 2024. It is now read-only.

Add StreamExtensions.firstOrNull #195

Merged
merged 3 commits into from
Aug 31, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.9.0

* Add `StreamExtensions.firstOrNull`.

## 2.8.2

* Deprecate `EventSinkBase`, `StreamSinkBase`, `IOSinkBase`.
Expand Down
20 changes: 20 additions & 0 deletions lib/src/stream_extensions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,24 @@ extension StreamExtensions<T> on Stream<T> {
sink.close();
}));
}

/// Returns the first data or error event emitted by [this] if it emits any,
/// or `null` if it emits a done event first.
Future<T?> get firstOrNull {
var completer = Completer<T?>.sync();
final subscription = listen(null);
Copy link
Contributor

@lrhn lrhn Aug 31, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can use cancelOnError: true. Then you can just tear off completer.completeErrorinstead of creating a new closure. It can even be:

  var subscription = listen(null, 
    onError: completer.completeError,
    onDone: completer.complete, // The argument is optional if the type is nullable!
    cancelOnError: true);
  subscription.onData((event) {
    subscription.cancel();
    completer.complete(event);
  });

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered doing it that way, but I think the symmetry between the onData and onError events is easier to follow than having one of them manually cancel the subscription and the other cancel it through cancelOnError.

subscription
..onData((event) {
subscription.cancel();
completer.complete(event);
})
..onError((Object error, StackTrace stackTrace) {
subscription.cancel();
completer.completeError(error, stackTrace);
})
..onDone(() {
completer.complete(null);
});
return completer.future;
}
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: async
version: 2.8.3
version: 2.9.0-dev

description: Utility functions and classes related to the 'dart:async' library.
repository: https://github.com/dart-lang/async
Expand Down
26 changes: 26 additions & 0 deletions test/stream_extensions_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,30 @@ void main() {
expect(() => Stream.fromIterable([1]).slices(0), throwsRangeError);
});
});

group('.firstOrNull', () {
test('returns the first data event', () {
expect(
Stream.fromIterable([1, 2, 3, 4]).firstOrNull, completion(equals(1)));
});

test('returns the first error event', () {
expect(Stream.error('oh no').firstOrNull, throwsA('oh no'));
});

test('returns null for an empty stream', () {
expect(Stream.empty().firstOrNull, completion(isNull));
});

test('cancels the subscription after an event', () async {
var isCancelled = false;
var controller = StreamController<int>(onCancel: () {
isCancelled = true;
});
controller.add(1);

await expectLater(controller.stream.firstOrNull, completion(equals(1)));
expect(isCancelled, isTrue);
});
});
}