Skip to content
Merged
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ Task.FromResult("not-a-url") //

The `IfFulfilled` and `IfFaulted` methods can be used to perform side effects such as logging when the `Task<T>` is in the fulfilled or faulted state, respectively.

**NOTE**: These functions do _not_ trap errors. If the function passed to them throws an exception, the result is a faulted task with that exception.

```c#
HttpClient client; // Assuming this is coming from an HttpClientFactory or injected or whatever

Expand Down Expand Up @@ -155,6 +157,29 @@ Task.FromException<int>(new ArgumentException())
))
```

#### InvokeIf

`InvokeIf` can be used to conditionally invoke a function. This is most useful for side effects.

```c#
Task.FromResult(someUrl)
.Then(httpClient.GetAsync)
.IfFulfilled(TaskExtras.InvokeIf(
httpResponse => !httpResponse.IsSuccessStatusCode,
httpResponse => _logger.LogWarning("Got '{StatusCode}' response from server", httpResponse.StatusCode)
);
```

However, it can be used with `.Then` as well:

```c#
Task.FromResult(4)
.Then(InvokeIf(
value => value % 2 == 0,
value => value + 1
);
```

[bluebird]: http://bluebirdjs.com/docs/getting-started.html
[MONADS.md]: ./MONADS.md
[nuget.org]: https://www.nuget.org/packages/RLC.TaskChaining/
2 changes: 1 addition & 1 deletion project-metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "task-chaining",
"description": "Extension methods to System.Threading.Task to allow Promise-like chaining",
"title": "TaskChaining",
"version": "2.18.5",
"version": "2.19.0",
"ciEnvironment": {
"variables": [
{
Expand Down
1 change: 1 addition & 0 deletions src/TaskExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ private static Exception HandleCancellation<T>(this Task<T> task)
/// <typeparam name="T">The task's underlying type.</typeparam>
/// <typeparam name="TR">The type of the other input value to <paramref name="morphismTask"/>.</typeparam>
/// <typeparam name="TNext">The transformed type.</typeparam>
/// <param name="task">The task.</param>
/// <param name="morphismTask">A <see cref="Task{Func{T, TR, TNext}}"/> containing the transformation function. </param>
/// <returns>The transformed task.</returns>
public static Task<TNext> Ap<T, TNext>(
Expand Down
18 changes: 8 additions & 10 deletions src/TaskExtensionsIfFaulted.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
using System;
using System.Threading;
using System.Threading.Tasks;

namespace RLC.TaskChaining;

using static TaskStatics;

public static partial class TaskExtensions
{
/// <summary>
/// Performs an action if the <see name="Task{T}"/> is in a faulted state.
/// </summary>
/// <typeparam name="T">The task's underlying type.</typeparam>
/// <param name="task">The task.</param>
/// <param name="onFaulted">The action to perform if the task is faulted.</param>
/// <returns>The task.</returns>
public static Task<T> IfFaulted<T>(this Task<T> task, Action<Exception> onFaulted)
Expand All @@ -27,6 +25,7 @@ public static Task<T> IfFaulted<T>(this Task<T> task, Action<Exception> onFaulte
/// </summary>
/// <typeparam name="T">The task's underlying type.</typeparam>
/// <typeparam name="R">The output task's underlying type.</typeparam>
/// <param name="task">The task.</param>
/// <param name="onFaulted">The function to execute if the task is faulted.</param>
/// <returns>The task.</returns>
public static Task<T> IfFaulted<T, R>(this Task<T> task, Func<Exception, Task<R>> onFaulted)
Expand All @@ -37,13 +36,11 @@ public static Task<T> IfFaulted<T, R>(this Task<T> task, Func<Exception, Task<R>
Exception taskException = PotentiallyUnwindException(continuationTask.Exception!);

return Task.FromException<R>(PotentiallyUnwindException(continuationTask.Exception!))
.Catch<R>(ex => onFaulted(ex))
.Then(
_ => Task.FromException<T>(taskException),
_ => Task.FromException<T>(taskException)
);
.Catch(onFaulted)
.Then(_ => Task.FromException<T>(taskException));
}
else if (continuationTask.IsCanceled)

if (continuationTask.IsCanceled)
{
try
{
Expand All @@ -64,8 +61,9 @@ public static Task<T> IfFaulted<T, R>(this Task<T> task, Func<Exception, Task<R>
/// Executes a function and throws away the result if the <see name="Task{T}"/> is in a faulted state.
/// </summary>
/// <typeparam name="T">The task's underlying type.</typeparam>
/// <param name="task">The task.</param>
/// <param name="onFaulted">The function to execute if the task is faulted.</param>
/// <returns>The task.</returns>
public static Task<T> IfFaulted<T>(this Task<T> task, Func<Exception, Task> onFaulted)
=> task.IfFaulted<T, T>(exception => onFaulted(exception).ContinueWith(continuationTask => Task.FromException<T>(exception)).Unwrap());
=> task.IfFaulted<T, T>(exception => onFaulted(exception).ContinueWith(_ => Task.FromException<T>(exception)).Unwrap());
}
33 changes: 21 additions & 12 deletions src/TaskExtensionsIfFulfilled.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
using System;
using System.Threading;
using System.Threading.Tasks;

namespace RLC.TaskChaining;

using static TaskStatics;

public static partial class TaskExtensions
{
/// <summary>
/// Performs an action if the <see name="Task{T}"/> is in a fulfilled state.
/// </summary>
/// <typeparam name="T">The task's underlying type.</typeparam>
/// <param name="task">The task.</param>
/// <param name="consumer">The action to perform if the task is fulfilled.</param>
/// <returns>The task.</returns>
public static Task<T> IfFulfilled<T>(this Task<T> task, Action<T> consumer)
Expand All @@ -26,28 +24,39 @@ public static Task<T> IfFulfilled<T>(this Task<T> task, Action<T> consumer)
/// <param name="func">The function to execute if the task is fulfilled.</param>
/// <returns>The task.</returns>
public static Task<T> IfFulfilled<T, R>(this Task<T> task, Func<T, Task<R>> func)
=> task.ContinueWith(async continuationTask =>
=> task.ContinueWith(continuationTask =>
{
if (continuationTask.IsFaulted || continuationTask.IsCanceled)
{
return continuationTask;
}
else
{
T value = await continuationTask;

return Task.FromResult(value).Then(func).Then(_ => value, _ => value);
}
}).Unwrap().Unwrap();
return continuationTask.Then(value =>
{
func(value);
return value;
});
}).Unwrap();

/// <summary>
/// Executes a function and throws away the result if the <see name="Task{T}"/> is in a fulfilled state.
/// </summary>
/// <typeparam name="T">The task's underlying type.</typeparam>
/// <typeparam name="R">The type of the discarded result of <paramref name="func"/>.</typeparam>
/// <param name="task">The task.</param>
/// <param name="func">The function to execute if the task is fulfilled.</param>
/// <returns>The task.</returns>
public static Task<T> IfFulfilled<T>(this Task<T> task, Func<T, Task> func)
=> task.IfFulfilled<T, T>(value => Task.FromResult(value).Then(func).Then(_ => value, _ => value));
=> task.ContinueWith(continuationTask =>
{
if (continuationTask.IsFaulted || continuationTask.IsCanceled)
{
return continuationTask;
}

return continuationTask.Then(async value =>
{
await func(value);
return value;
});
}).Unwrap();
}
123 changes: 101 additions & 22 deletions src/TaskExtensionsTap.cs
Original file line number Diff line number Diff line change
@@ -1,109 +1,188 @@
using System;
using System.Threading;
using System.Threading.Tasks;

namespace RLC.TaskChaining;

using static TaskStatics;

public static partial class TaskExtensions
{
/// <summary>
/// Executes a function and discards the result on a <see name="Task{T}"/> whether it is in a fulfilled or faulted state.
/// </summary>
/// <remarks>This method is useful if you need to perform a side effect without altering the <see name"Task{T}"/>'s
/// <remarks>This method is useful if you need to perform a side effect without altering the <see name="Task{T}"/>'s
/// value, such as logging.</remarks>
/// <typeparam name="T">The task's underlying type.</typeparam>
/// <param name="task">The task.</param>
/// <param name="onFulfilled">The function to execute if the task is fulfilled.</param>
/// <param name="onFaulted">The function to execute if the task is faulted.</param>
/// <returns>The task.</returns>
public static Task<T> Tap<T>(this Task<T> task, Action<T> onFulfilled, Action<Exception> onFaulted)
=> task.IfFulfilled(onFulfilled).IfFaulted(onFaulted);
=> task.ContinueWith(continuationTask =>
{
if (continuationTask.IsCanceled)
{
return continuationTask;
}

return continuationTask.IsFaulted
? continuationTask.IfFaulted(onFaulted)
: continuationTask.IfFulfilled(onFulfilled);
}).Unwrap();

/// <summary>
/// Executes a function and discards the result on a <see name="Task{T}"/> whether it is in a fulfilled or faulted state.
/// </summary>
/// <remarks>This method is useful if you need to perform a side effect without altering the <see name"Task{T}"/>'s
/// <remarks>This method is useful if you need to perform a side effect without altering the <see name="Task{T}"/>'s
/// value, such as logging.</remarks>
/// <typeparam name="T">The task's underlying type.</typeparam>
/// <typeparam name="R">The output type of the <paramref name="onFaulted" /> function.
/// <typeparam name="R">The output type of the <paramref name="onFaulted" /> function.</typeparam>
/// <param name="task">The task.</param>
/// <param name="onFulfilled">The function to execute if the task is fulfilled.</param>
/// <param name="onFaulted">The function to execute if the task is faulted.</param>
/// <returns>The task.</returns>
public static Task<T> Tap<T, R>(this Task<T> task, Action<T> onFulfilled, Func<Exception, Task<R>> onFaulted)
=> task.IfFulfilled(onFulfilled).IfFaulted(onFaulted);
=> task.Tap(
value =>
{
onFulfilled(value);
return Task.FromResult(value);
},
onFaulted
);

/// <summary>
/// Executes a function and discards the result on a <see name="Task{T}"/> whether it is in a fulfilled or faulted state.
/// </summary>
/// <remarks>This method is useful if you need to perform a side effect without altering the <see name"Task{T}"/>'s
/// <remarks>This method is useful if you need to perform a side effect without altering the <see name="Task{T}"/>'s
/// value, such as logging.</remarks>
/// <typeparam name="T">The task's underlying type.</typeparam>
/// <param name="task">The task.</param>
/// <param name="onFulfilled">The function to execute if the task is fulfilled.</param>
/// <param name="onFaulted">The function to execute if the task is faulted.</param>
/// <returns>The task.</returns>
public static Task<T> Tap<T>(this Task<T> task, Action<T> onFulfilled, Func<Exception, Task> onFaulted)
=> task.IfFulfilled(onFulfilled).IfFaulted(onFaulted);
=> task.ContinueWith(continuationTask =>
{
if (continuationTask.IsCanceled)
{
return continuationTask;
}

return continuationTask.IsFaulted
? continuationTask.IfFaulted(onFaulted)
: continuationTask.IfFulfilled(onFulfilled);
}).Unwrap();

/// <summary>
/// Executes a function and discards the result on a <see name="Task{T}"/> whether it is in a fulfilled or faulted state.
/// </summary>
/// <remarks>This method is useful if you need to perform a side effect without altering the <see name"Task{T}"/>'s
/// <remarks>This method is useful if you need to perform a side effect without altering the <see name="Task{T}"/>'s
/// value, such as logging.</remarks>
/// <typeparam name="T">The task's underlying type.</typeparam>
/// <typeparam name="R">The output type of the <paramref name="onFaulted" /> function.
/// <typeparam name="R">The output type of the <paramref name="onFaulted" /> function.</typeparam>
/// <param name="task">The task.</param>
/// <param name="onFulfilled">The action to perform if the task is fulfilled.</param>
/// <param name="onFaulted">The function to execute if the task is faulted.</param>
/// <returns>The task.</returns>
public static Task<T> Tap<T, R>(this Task<T> task, Func<T, Task<R>> onFulfilled, Action<Exception> onFaulted)
=> task.IfFulfilled(onFulfilled).IfFaulted(onFaulted);
=> task.ContinueWith(continuationTask =>
{
if (continuationTask.IsCanceled)
{
return continuationTask;
}

return continuationTask.IsFaulted
? continuationTask.IfFaulted(onFaulted)
: continuationTask.IfFulfilled(onFulfilled);
}).Unwrap();

/// <summary>
/// Executes a function and discards the result on a <see name="Task{T}"/> whether it is in a fulfilled or faulted state.
/// </summary>
/// <remarks>This method is useful if you need to perform a side effect without altering the <see name"Task{T}"/>'s
/// <remarks>This method is useful if you need to perform a side effect without altering the <see name="Task{T}"/>'s
/// value, such as logging.</remarks>
/// <typeparam name="T">The task's underlying type.</typeparam>
/// <param name="task">The task.</param>
/// <param name="onFulfilled">The function to execute if the task is fulfilled.</param>
/// <param name="onFaulted">The function to execute if the task is faulted.</param>
/// <returns>The task.</returns>
public static Task<T> Tap<T>(this Task<T> task, Func<T, Task> onFulfilled, Action<Exception> onFaulted)
=> task.IfFulfilled(onFulfilled).IfFaulted(onFaulted);
=> task.ContinueWith(continuationTask =>
{
if (continuationTask.IsCanceled)
{
return continuationTask;
}

return continuationTask.IsFaulted
? continuationTask.IfFaulted(onFaulted)
: continuationTask.IfFulfilled(onFulfilled);
}).Unwrap();

/// <summary>
/// Executes a function and discards the result on a <see name="Task{T}"/> whether it is in a fulfilled or faulted state.
/// </summary>
/// <remarks>This method is useful if you need to perform a side effect without altering the <see name"Task{T}"/>'s
/// <remarks>This method is useful if you need to perform a side effect without altering the <see name="Task{T}"/>'s
/// value, such as logging.</remarks>
/// <typeparam name="T">The task's underlying type.</typeparam>
/// <typeparam name="R">The output type of the <paramref name="onFaulted" /> function.
/// <typeparam name="R">The output type of the <paramref name="onFaulted" /> function.</typeparam>
/// <param name="task">The task.</param>
/// <param name="onFulfilled">The function to execute if the task is fulfilled.</param>
/// <param name="onFaulted">The function to execute if the task is faulted.</param>
/// <returns>The task.</returns>
public static Task<T> Tap<T, R, S>(this Task<T> task, Func<T, Task<R>> onFulfilled, Func<Exception, Task<S>> onFaulted)
=> task.IfFulfilled(onFulfilled).IfFaulted(onFaulted);
=> task.ContinueWith(continuationTask =>
{
if (continuationTask.IsCanceled)
{
return continuationTask;
}

return continuationTask.IsFaulted
? continuationTask.IfFaulted(onFaulted)
: continuationTask.IfFulfilled(onFulfilled);
}).Unwrap();

/// <summary>
/// Executes a function and discards the result on a <see name="Task{T}"/> whether it is in a fulfilled or faulted state.
/// </summary>
/// <remarks>This method is useful if you need to perform a side effect without altering the <see name"Task{T}"/>'s
/// <remarks>This method is useful if you need to perform a side effect without altering the <see name="Task{T}"/>'s
/// value, such as logging.</remarks>
/// <typeparam name="T">The task's underlying type.</typeparam>
/// <param name="task">The task.</param>
/// <param name="onFulfilled">The function to execute if the task is fulfilled.</param>
/// <param name="onFaulted">The function to execute if the task is faulted.</param>
/// <returns>The task.</returns>
public static Task<T> Tap<T>(this Task<T> task, Func<T, Task> onFulfilled, Func<Exception, Task> onFaulted)
=> task.IfFulfilled(onFulfilled).IfFaulted(onFaulted);
=> task.ContinueWith(continuationTask =>
{
if (continuationTask.IsCanceled)
{
return continuationTask;
}

return continuationTask.IsFaulted
? continuationTask.IfFaulted(onFaulted)
: continuationTask.IfFulfilled(onFulfilled);
}).Unwrap();

/// <summary>
/// Executes a function and discards the result on a <see name="Task{T}"/> whether it is in a fulfilled or faulted state.
/// </summary>
/// <remarks>This method is useful if you need to perform a side effect without altering the <see name"Task{T}"/>'s
/// <remarks>This method is useful if you need to perform a side effect without altering the <see name="Task{T}"/>'s
/// value, such as logging.</remarks>
/// <typeparam name="T">The task's underlying type.</typeparam>
/// <param name="task">The task.</param>
/// <param name="onFulfilled">The function to execute if the task is fulfilled.</param>
/// <param name="onFaulted">The function to execute if the task is faulted.</param>
/// <returns>The task.</returns>
public static Task<T> Tap<T, R>(this Task<T> task, Func<T, Task> onFulfilled, Func<Exception, Task<R>> onFaulted)
=> task.IfFulfilled(onFulfilled).IfFaulted(onFaulted);
=> task.Tap(
value =>
{
onFulfilled(value);
return Task.FromResult(value);
},
onFaulted
);
}
Loading