Skip to content

Commit 37df918

Browse files
authored
Add (and use internally) Async.Await (#591)
1 parent 464cf28 commit 37df918

File tree

4 files changed

+78
-17
lines changed

4 files changed

+78
-17
lines changed

src/FSharpPlus/Extensions/Async.fs

+8-8
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ namespace FSharpPlus
44
[<RequireQualifiedAccess>]
55
module Async =
66

7-
open System
7+
open FSharpPlus.Extensions
88

99
/// <summary>Creates an async workflow from another workflow 'x', mapping its result with 'f'.</summary>
1010
let map f x = async.Bind (x, async.Return << f)
@@ -43,8 +43,8 @@ module Async =
4343
let! ct = Async.CancellationToken
4444
let x = Async.StartImmediateAsTask (x, ct)
4545
let y = Async.StartImmediateAsTask (y, ct)
46-
let! x' = Async.AwaitTask x
47-
let! y' = Async.AwaitTask y
46+
let! x' = Async.Await x
47+
let! y' = Async.Await y
4848
return f x' y' }
4949
#endif
5050

@@ -62,9 +62,9 @@ module Async =
6262
let x = Async.StartImmediateAsTask (x, ct)
6363
let y = Async.StartImmediateAsTask (y, ct)
6464
let z = Async.StartImmediateAsTask (z, ct)
65-
let! x' = Async.AwaitTask x
66-
let! y' = Async.AwaitTask y
67-
let! z' = Async.AwaitTask z
65+
let! x' = Async.Await x
66+
let! y' = Async.Await y
67+
let! z' = Async.Await z
6868
return f x' y' z' }
6969
#endif
7070

@@ -83,8 +83,8 @@ module Async =
8383
let! ct = Async.CancellationToken
8484
let x = Async.StartImmediateAsTask (x, ct)
8585
let y = Async.StartImmediateAsTask (y, ct)
86-
let! x' = Async.AwaitTask x
87-
let! y' = Async.AwaitTask y
86+
let! x' = Async.Await x
87+
let! y' = Async.Await y
8888
return x', y' }
8989
#endif
9090

src/FSharpPlus/Extensions/AsyncEnumerable.fs

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ open System
66
open System.Threading
77
open System.Threading.Tasks
88
open FSharpPlus.Data
9+
open FSharpPlus.Extensions
910

1011
/// Additional operations on Observable<'T>
1112
[<RequireQualifiedAccess>]
@@ -17,11 +18,11 @@ module AsyncEnumerable =
1718
use _ =
1819
{ new IDisposable with
1920
member _.Dispose () =
20-
e.DisposeAsync().AsTask () |> Async.AwaitTask |> Async.RunSynchronously }
21+
e.DisposeAsync().AsTask () |> Async.Await |> Async.RunSynchronously }
2122

2223
let mutable currentResult = true
2324
while currentResult do
24-
let! r = e.MoveNextAsync().AsTask () |> Async.AwaitTask |> SeqT.lift
25+
let! r = e.MoveNextAsync().AsTask () |> Async.Await |> SeqT.lift
2526
currentResult <- r
2627
if r then yield e.Current
2728
}

src/FSharpPlus/Extensions/Extensions.fs

+66-6
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,67 @@ module Extensions =
6464

6565
type Async<'t> with
6666

67+
static member internal Map f x = async.Bind (x, async.Return << f)
68+
6769
#if !FABLE_COMPILER
70+
71+
// See https://github.com/fsharp/fslang-suggestions/issues/840
72+
73+
/// <summary>Return an asynchronous computation that will wait for the given task to complete and return
74+
/// its result.</summary>
75+
///
76+
/// <param name="task">The task to await.</param>
77+
///
78+
/// <remarks>Prefer this over <c>Async.AwaitTask</c>.
79+
///
80+
/// If an exception occurs in the asynchronous computation then an exception is re-raised by this function.
81+
///
82+
/// If the task is cancelled then <see cref="F:System.Threading.Tasks.TaskCanceledException"/> is raised. Note
83+
/// that the task may be governed by a different cancellation token to the overall async computation where the
84+
/// Await occurs. In practice you should normally start the task with the cancellation token returned by
85+
/// <c>let! ct = Async.CancellationToken</c>, and catch any <see cref="F:System.Threading.Tasks.TaskCanceledException"/>
86+
/// at the point where the overall async is started.
87+
/// </remarks>
88+
static member Await (task: Task<'T>) : Async<'T> =
89+
Async.FromContinuations (fun (sc, ec, _) ->
90+
task.ContinueWith (fun (task: Task<'T>) ->
91+
if task.IsFaulted then
92+
let e = task.Exception
93+
if e.InnerExceptions.Count = 1 then ec e.InnerExceptions[0]
94+
else ec e
95+
elif task.IsCanceled then ec (TaskCanceledException ())
96+
else sc task.Result)
97+
|> ignore)
98+
99+
100+
/// <summary>Return an asynchronous computation that will wait for the given task to complete and return
101+
/// its result.</summary>
102+
///
103+
/// <param name="task">The task to await.</param>
104+
///
105+
/// <remarks>Prefer this over <c>Async.AwaitTask</c>.
106+
///
107+
/// If an exception occurs in the asynchronous computation then an exception is re-raised by this function.
108+
///
109+
/// If the task is cancelled then <see cref="F:System.Threading.Tasks.TaskCanceledException"/> is raised. Note
110+
/// that the task may be governed by a different cancellation token to the overall async computation where the
111+
/// Await occurs. In practice you should normally start the task with the cancellation token returned by
112+
/// <c>let! ct = Async.CancellationToken</c>, and catch any <see cref="F:System.Threading.Tasks.TaskCanceledException"/>
113+
/// at the point where the overall async is started.
114+
/// </remarks>
115+
static member Await (task: Task) : Async<unit> =
116+
Async.FromContinuations (fun (sc, ec, _) ->
117+
task.ContinueWith (fun (task: Task) ->
118+
if task.IsFaulted then
119+
let e = task.Exception
120+
if e.InnerExceptions.Count = 1 then ec e.InnerExceptions[0]
121+
else ec e
122+
elif task.IsCanceled then ec (TaskCanceledException ())
123+
else sc ())
124+
|> ignore)
125+
126+
127+
68128
/// Combine all asyncs in one, chaining them in sequence order.
69129
static member Sequence (t:seq<Async<_>>) : Async<seq<_>> = async {
70130
let startImmediateAsTask ct a =
@@ -102,26 +162,26 @@ module Extensions =
102162
/// Creates an async Result from a Result where the Ok case is async.
103163
static member Sequence (t: Result<Async<'T>, 'Error>) : Async<Result<'T,'Error>> =
104164
match t with
105-
| Ok a -> Async.map Ok a
165+
| Ok a -> Async.Map Ok a
106166
| Error e -> async.Return (Error e)
107167

108168
/// Creates an async Choice from a Choice where the Choice1Of2 case is async.
109169
static member Sequence (t: Choice<Async<'T>, 'Choice2Of2>) : Async<Choice<'T,'Choice2Of2>> =
110170
match t with
111-
| Choice1Of2 a -> Async.map Choice1Of2 a
171+
| Choice1Of2 a -> Async.Map Choice1Of2 a
112172
| Choice2Of2 e -> async.Return (Choice2Of2 e)
113173

114174
/// Creates an async Result from a Result where both cases are async.
115175
static member Bisequence (t: Result<Async<'T>, Async<'Error>>) : Async<Result<'T,'Error>> =
116176
match t with
117-
| Ok a -> Async.map Ok a
118-
| Error e -> Async.map Error e
177+
| Ok a -> Async.Map Ok a
178+
| Error e -> Async.Map Error e
119179

120180
/// Creates an async Choice from a Choice where both cases are async.
121181
static member Bisequence (t: Choice<Async<'T>, Async<'Choice2Of2>>) : Async<Choice<'T,'Choice2Of2>> =
122182
match t with
123-
| Choice1Of2 a -> Async.map Choice1Of2 a
124-
| Choice2Of2 e -> Async.map Choice2Of2 e
183+
| Choice1Of2 a -> Async.Map Choice1Of2 a
184+
| Choice2Of2 e -> Async.Map Choice2Of2 e
125185

126186
type Option<'t> with
127187

src/FSharpPlus/FSharpPlus.fsproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@
5454
<Compile Include="Extensions/Enumerator.fs" />
5555
<Compile Include="Extensions/Task.fs" />
5656
<Compile Include="Extensions/ValueTask.fs" />
57-
<Compile Include="Extensions/Async.fs" />
5857
<Compile Include="Extensions/Extensions.fs" />
58+
<Compile Include="Extensions/Async.fs" />
5959
<Compile Include="Extensions/Tuple.fs" />
6060
<Compile Include="Extensions/ValueTuple.fs" />
6161
<Compile Include="Data/NonEmptySeq.fs" />

0 commit comments

Comments
 (0)