Skip to main content

Outcome

The Outcome<T> class represents the result of a resilience pipeline execution, containing either a successful result or exception information.

Overview

The Outcome class provides a type-safe way to handle both successful and failed operations. It encapsulates the result value, exception details, and provides utility methods for outcome analysis.

class Outcome<T> {
const Outcome.success(T result);
const Outcome.failure(Exception exception);

bool get hasResult;
bool get hasException;
T get result;
Exception get exception;

T getResultOrThrow();
T getResultOrDefault(T defaultValue);
T? getResultOrNull();

Outcome<U> map<U>(U Function(T) mapper);
Outcome<U> flatMap<U>(Outcome<U> Function(T) mapper);
Outcome<T> recover(T Function(Exception) recovery);
Outcome<T> onSuccess(void Function(T) action);
Outcome<T> onFailure(void Function(Exception) action);
}

Constructors

Outcome.success(T result)

Creates a successful outcome with the specified result.

Parameters:

  • result - The successful result value
final outcome = Outcome.success('Hello World');
print(outcome.hasResult); // true
print(outcome.result); // Hello World

Outcome.failure(Exception exception)

Creates a failed outcome with the specified exception.

Parameters:

  • exception - The exception that occurred
final outcome = Outcome.failure(TimeoutException('Request timed out'));
print(outcome.hasException); // true
print(outcome.exception); // TimeoutException

Properties

hasResult

Gets a value indicating whether the outcome contains a successful result.

Type: bool

if (outcome.hasResult) {
print('Operation succeeded: ${outcome.result}');
}

hasException

Gets a value indicating whether the outcome contains an exception.

Type: bool

if (outcome.hasException) {
print('Operation failed: ${outcome.exception}');
}

result

Gets the successful result value. Throws if the outcome is a failure.

Type: T

Throws: StateError if the outcome is a failure

try {
final value = outcome.result;
print('Success: $value');
} on StateError {
print('Cannot access result on failed outcome');
}

exception

Gets the exception. Throws if the outcome is successful.

Type: Exception

Throws: StateError if the outcome is successful

try {
final error = outcome.exception;
print('Error: $error');
} on StateError {
print('Cannot access exception on successful outcome');
}

Methods

getResultOrThrow()

Gets the result value or throws the contained exception.

Returns: T - The result value

Throws: The contained exception if the outcome is a failure

try {
final value = outcome.getResultOrThrow();
print('Success: $value');
} catch (e) {
print('Failed: $e');
}

getResultOrDefault(T defaultValue)

Gets the result value or returns the specified default value if the outcome is a failure.

Parameters:

  • defaultValue - The value to return if the outcome is a failure

Returns: T - The result value or default value

final value = outcome.getResultOrDefault('default');
print('Value: $value'); // Returns result or 'default'

getResultOrNull()

Gets the result value or returns null if the outcome is a failure.

Returns: T? - The result value or null

final value = outcome.getResultOrNull();
if (value != null) {
print('Success: $value');
} else {
print('Operation failed');
}

map<U>(U Function(T) mapper)

Transforms the successful result using the specified mapper function. If the outcome is a failure, returns a new failure outcome with the same exception.

Parameters:

  • mapper - Function to transform the result

Returns: Outcome<U> - New outcome with transformed result

final stringOutcome = Outcome.success(42);
final doubledOutcome = stringOutcome.map((value) => value * 2);
print(doubledOutcome.result); // 84

final lengthOutcome = stringOutcome.map((value) => value.toString().length);
print(lengthOutcome.result); // 2

flatMap<U>(Outcome<U> Function(T) mapper)

Transforms the successful result using a function that returns an Outcome. Useful for chaining operations that might fail.

Parameters:

  • mapper - Function that transforms the result to another Outcome

Returns: Outcome<U> - The outcome returned by the mapper function

final numberOutcome = Outcome.success('42');
final parsedOutcome = numberOutcome.flatMap((str) {
try {
return Outcome.success(int.parse(str));
} catch (e) {
return Outcome.failure(FormatException('Invalid number: $str'));
}
});

recover(T Function(Exception) recovery)

Recovers from a failure by providing an alternative result. If the outcome is successful, returns the original outcome.

Parameters:

  • recovery - Function to provide alternative result

Returns: Outcome<T> - Recovered outcome or original if successful

final failedOutcome = Outcome.failure(TimeoutException('Timeout'));
final recoveredOutcome = failedOutcome.recover((exception) => 'fallback-value');
print(recoveredOutcome.result); // 'fallback-value'

onSuccess(void Function(T) action)

Executes the specified action if the outcome is successful.

Parameters:

  • action - Action to execute with the result

Returns: Outcome<T> - The original outcome for chaining

outcome
.onSuccess((value) => print('Success: $value'))
.onFailure((error) => print('Error: $error'));

onFailure(void Function(Exception) action)

Executes the specified action if the outcome is a failure.

Parameters:

  • action - Action to execute with the exception

Returns: Outcome<T> - The original outcome for chaining

outcome
.onSuccess((value) => print('Success: $value'))
.onFailure((error) => print('Error: $error'));

Usage Examples

Basic Outcome Handling

Future<Outcome<String>> fetchData() async {
try {
final data = await apiCall();
return Outcome.success(data);
} catch (e) {
return Outcome.failure(e as Exception);
}
}

final outcome = await fetchData();
if (outcome.hasResult) {
print('Data: ${outcome.result}');
} else {
print('Error: ${outcome.exception}');
}

Pipeline Execution with Outcomes

final outcome = await pipeline.executeAndCapture<String>((context) async {
return await fetchUserData(userId);
});

final result = outcome.getResultOrDefault('Guest User');
print('User: $result');

Chaining Operations

final outcome = await pipeline.executeAndCapture<String>((context) async {
return await fetchUserData(userId);
});

final processedOutcome = outcome
.map((userData) => userData.toUpperCase())
.onSuccess((data) => print('Processed: $data'))
.onFailure((error) => logger.error('Failed to fetch user data', error));

Complex Transformations

final userIdOutcome = Outcome.success('12345');

final userDataOutcome = await userIdOutcome.flatMap((userId) async {
try {
final userData = await fetchUserData(userId);
return Outcome.success(userData);
} catch (e) {
return Outcome.failure(e as Exception);
}
});

final finalResult = userDataOutcome
.map((userData) => userData.displayName)
.recover((error) => 'Unknown User')
.getResultOrThrow();

Error Recovery Patterns

final outcome = await pipeline.executeAndCapture<List<String>>((context) async {
return await fetchDataList();
});

final safeResult = outcome
.recover((error) {
// Return cached data or empty list
return getCachedData() ?? <String>[];
})
.map((list) => list.where((item) => item.isNotEmpty).toList())
.getResultOrDefault(<String>[]);

Conditional Processing

final outcome = await pipeline.executeAndCapture<int>((context) async {
return await calculateValue();
});

final processedValue = outcome
.onSuccess((value) {
if (value > 100) {
logger.warn('High value detected: $value');
}
})
.onFailure((error) {
metrics.incrementCounter('calculation_failures');
})
.map((value) => value.clamp(0, 100))
.getResultOrDefault(0);

Best Practices

Always Handle Both Cases

// Good
if (outcome.hasResult) {
handleSuccess(outcome.result);
} else {
handleFailure(outcome.exception);
}

// Or use methods
outcome
.onSuccess(handleSuccess)
.onFailure(handleFailure);

Use Safe Accessors

// Good - Safe access
final value = outcome.getResultOrDefault('fallback');

// Avoid - Can throw
final value = outcome.result; // Throws if failed

Chain Operations Safely

final result = outcome
.map(transformData)
.recover(provideDefault)
.getResultOrThrow();