Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

useOr #28

Merged
merged 1 commit into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
15 changes: 15 additions & 0 deletions lib/computed.dart
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ class Computed<T> {
/// Throws [CyclicUseException] if this usage would cause a cyclic dependency.
T get use => _impl.use;

/// As [use], but returns [value] instead of throwing [NoValueException].
T useOr(T value) {
try {
return use;
} on NoValueException {
return value;
}
}

/// Returns the result of this computation during the last run of the current computation which notified the current computation's downstream, if one exists.
/// If called on the current computation, returns its last result which was different to the previous one.
///
Expand Down Expand Up @@ -123,6 +132,9 @@ extension StreamComputedExtension<T> on Stream<T> {
/// Throws [NoValueException] if this stream does not have a known value yet.
T get use => StreamComputedExtensionImpl<T>(this).use;

/// As [use], but returns [value] instead of throwing [NoValueException].
T useOr(T value) => StreamComputedExtensionImpl<T>(this).useOr(value);

/// If this stream has produced a value or error since the last time the current computation notified its downstream, runs the given functional on the value or error produced by this stream.
///
/// If no onError is provided and the stream has produced an error, the current computation
Expand Down Expand Up @@ -169,6 +181,9 @@ extension FutureComputedExtension<T> on Future<T> {
/// If the future gets resolved with an error, throws it.
/// Throws [NoValueException] if this future has not been resolved yet.
T get use => FutureComputedExtensionImpl<T>(this).use;

/// As [use], but returns [value] instead of throwing [NoValueException].
T useOr(T value) => FutureComputedExtensionImpl<T>(this).useOr(value);
}

/// Thrown when a data source [use]d by a computation
Expand Down
10 changes: 10 additions & 0 deletions lib/src/future_extension.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'package:computed/computed.dart';

import 'computed.dart';
import 'data_source_subscription.dart';

Expand All @@ -10,6 +12,14 @@ class FutureComputedExtensionImpl<T> {
return caller.dataSourceUse(
f, (router) => _FutureDataSourceSubscription<T>(f, router), null);
}

T useOr(T value) {
try {
return use;
} on NoValueException {
return value;
}
}
}

class _FutureDataSourceSubscription<T> implements DataSourceSubscription<T> {
Expand Down
10 changes: 10 additions & 0 deletions lib/src/stream_extension.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'dart:async';

import 'package:computed/computed.dart';

import 'computed.dart';
import 'data_source_subscription.dart';

Expand All @@ -18,6 +20,14 @@ class StreamComputedExtensionImpl<T> {
null);
}

T useOr(T value) {
try {
return use;
} on NoValueException {
return value;
}
}

void react(void Function(T) onData, void Function(Object)? onError) {
final caller = GlobalCtx.currentComputation;
caller.dataSourceReact<T>(
Expand Down
80 changes: 80 additions & 0 deletions test/computed_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,31 @@ void main() {
sub.cancel();
});

test('useOr works', () async {
final controller = StreamController<int>.broadcast(
sync: true); // Use a broadcast stream to make debugging easier
final source = controller.stream;

var lCnt = 0;
int? lastRes;

final sub = Computed(() => source.useOr(21) * 2).listen((event) {
lCnt++;
lastRes = event;
}, (e) => fail(e.toString()));

expect(lCnt, 0);
await Future.value();
expect(lCnt, 1);
expect(lastRes, 42);

controller.add(0);
expect(lCnt, 2);
expect(lastRes, 0);

sub.cancel();
});

test('react does not memoize the values produced by the stream', () async {
final controller = StreamController<int>.broadcast(
sync: true); // Use a broadcast stream to make debugging easier
Expand Down Expand Up @@ -401,6 +426,33 @@ void main() {
sub.cancel();
});

test('useOr works', () async {
final completer = Completer<int>();
final future = completer.future;

final x = Computed(() => future.useOr(42));

var callCnt = 0;
int? lastEvent;

final sub = x.listen((event) {
callCnt++;
lastEvent = event;
}, (e) => fail(e.toString()));

expect(callCnt, 0);
await Future.value();
expect(callCnt, 1);
expect(lastEvent, 42);

completer.complete(1);
await Future.value();
expect(callCnt, 2);
expect(lastEvent, 1);

sub.cancel();
});

test('can pass rejections to computations', () async {
final completer = Completer<int>();
final future = completer.future;
Expand Down Expand Up @@ -511,6 +563,34 @@ void main() {
sub.cancel();
});

test('useOr works on computations', () async {
final controller = StreamController<int>.broadcast(
sync: true); // Use a broadcast stream to make debugging easier
final source = controller.stream;

int? lastRes;

final x2 = Computed(() => source.use * 2);

var lCnt = 0;

final sub = Computed(() => x2.useOr(41) + 1).listen((event) {
lCnt++;
lastRes = event;
}, (e) => fail(e.toString()));

expect(lCnt, 0);
await Future.value();
expect(lCnt, 1);
expect(lastRes, 42);

controller.add(0);
expect(lCnt, 2);
expect(lastRes, 1);

sub.cancel();
});

test('computations are memoized', () {
final controller = StreamController<int>.broadcast(
sync: true); // Use a broadcast stream to make debugging easier
Expand Down