Skip to content

Commit

Permalink
[JS/TS] Improvements to Async support (#4059)
Browse files Browse the repository at this point in the history
* [JS/TS] Report an error at compilation time when trying to use `Async.RunSynchronously`

* [JS/TS] Propagate non-captured exception when running `Async.Start` or `Async.StartImmediate`

* fix test for running on .NET

* adapt fcs-fable code to not use Async.RunSynchronously

* update changelogs
  • Loading branch information
MangelMaxime authored Feb 20, 2025
1 parent 3fe371f commit 9250574
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 18 deletions.
2 changes: 2 additions & 0 deletions src/Fable.Cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* [JS/TS] Fix #4025: No reflection info for pojos (by @alfonsogarciacaro)
* [JS/TS] Fix #4049: decimal/bigint to integer conversion checks (by @ncave)
* [JS/TS] Fix `decimal` to `char` conversion checks (by @ManngelMaxime)
* [JS/TS] Propagate non-captured exception when running `Async.Start` or `Async.StartImmediate` (by @MangelMaxime)
* [JS/TS] Report an error at compilation time when trying to use `Async.RunSynchronously` (by @MangelMaxime)

## 5.0.0-alpha.10 - 2025-02-16

Expand Down
2 changes: 2 additions & 0 deletions src/Fable.Compiler/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* [JS/TS] Fix #4025: No reflection info for pojos (by @alfonsogarciacaro)
* [JS/TS] Fix #4049: decimal/bigint to integer conversion checks (by @ncave)
* [JS/TS] Fix `decimal` to `char` conversion checks (by @ManngelMaxime)
* [JS/TS] Propagate non-captured exception when running `Async.Start` or `Async.StartImmediate` (by @MangelMaxime)
* [JS/TS] Report an error at compilation time when trying to use `Async.RunSynchronously` (by @MangelMaxime)

## 5.0.0-alpha.10 - 2025-02-16

Expand Down
2 changes: 1 addition & 1 deletion src/Fable.Transforms/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3622,7 +3622,6 @@ let asyncBuilder (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Exp

let asyncs com (ctx: Context) r t (i: CallInfo) (_: Expr option) (args: Expr list) =
match i.CompiledName with
// TODO: Throw error for RunSynchronously
| "Start" ->
"Async.Start will behave as StartImmediate" |> addWarning com ctx.InlinePath r

Expand All @@ -3634,6 +3633,7 @@ let asyncs com (ctx: Context) r t (i: CallInfo) (_: Expr option) (args: Expr lis
| "Catch" ->
Helper.LibCall(com, "Async", "catchAsync", t, args, i.SignatureArgTypes, genArgs = i.GenericArgs, ?loc = r)
|> Some
| "RunSynchronously" -> None
// Fable.Core extensions
| meth ->
Helper.LibCall(
Expand Down
27 changes: 13 additions & 14 deletions src/fable-library-ts/Async.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,14 @@ export function sleep(millisecondsDueTime: number) {
});
}

export function runSynchronously(): never {
throw new Error("Asynchronous code cannot be run synchronously in JS");
}

export function start<T>(computation: Async<T>, cancellationToken?: CancellationToken) {
return startWithContinuations(computation, cancellationToken);
return startWithContinuations(
computation,
emptyContinuation,
function (err) { throw err },
emptyContinuation,
cancellationToken
);
}

export function startImmediate<T>(computation: Async<T>, cancellationToken?: CancellationToken) {
Expand All @@ -173,19 +175,16 @@ export function startImmediate<T>(computation: Async<T>, cancellationToken?: Can

export function startWithContinuations<T>(
computation: Async<T>,
continuation?: Continuation<T> | CancellationToken,
exceptionContinuation?: Continuation<any>,
cancellationContinuation?: Continuation<any>,
continuation: Continuation<T>,
exceptionContinuation: Continuation<any>,
cancellationContinuation: Continuation<any>,
cancelToken?: CancellationToken) {
if (typeof continuation !== "function") {
cancelToken = continuation as CancellationToken;
continuation = undefined;
}

const trampoline = new Trampoline();
computation({
onSuccess: continuation ? continuation as Continuation<T> : emptyContinuation,
onError: exceptionContinuation ? exceptionContinuation : emptyContinuation,
onCancel: cancellationContinuation ? cancellationContinuation : emptyContinuation,
onError: exceptionContinuation,
onCancel: cancellationContinuation,
cancelToken: cancelToken ? cancelToken : defaultCancellationToken,
trampoline,
});
Expand Down
3 changes: 3 additions & 0 deletions src/fable-library-ts/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

* [JS/TS] Fix #4049: decimal/bigint to integer conversion checks (by @ncave)
* [JS/TS] Fix `decimal` to `char` conversion checks (by @ManngelMaxime)
* [JS/TS] Propagate non-captured exception when running `Async.Start` or `Async.StartImmediate` (by @MangelMaxime)
* [JS/TS] Remove `Async.RunSynchronously` (by @MangelMaxime)
* [JS/TS] Change signature of `startWithContinuations` to always require all its arguments (by @MangelMaxime)

## 2.0.0-beta.1 - 2025-02-16

Expand Down
13 changes: 11 additions & 2 deletions src/fcs-fable/src/Compiler/Service/FSharpCheckerResults.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3860,6 +3860,9 @@ type FSharpCheckProjectResults
|> Array.toSeq
#endif //!FABLE_COMPILER
| Choice2Of2 task ->
#if FABLE_COMPILER
seq {}
#else
Async.RunSynchronously(
async {
let! tcSymbolUses = task
Expand All @@ -3872,6 +3875,7 @@ type FSharpCheckProjectResults
},
?cancellationToken = cancellationToken
)
#endif //!FABLE_COMPILER

results
|> Seq.filter (fun symbolUse -> symbolUse.ItemOccurrence <> ItemOccurrence.RelatedText)
Expand All @@ -3890,7 +3894,7 @@ type FSharpCheckProjectResults

let cenv = SymbolEnv(tcGlobals, thisCcu, Some ccuSig, tcImports)

let tcSymbolUses =
let tcSymbolUses : TcSymbolUses seq =
match builderOrSymbolUses with
| Choice1Of2 builder ->
#if FABLE_COMPILER
Expand All @@ -3908,7 +3912,12 @@ type FSharpCheckProjectResults
| _ -> TcSymbolUses.Empty)
|> Array.toSeq
#endif //!FABLE_COMPILER
| Choice2Of2 tcSymbolUses -> Async.RunSynchronously(tcSymbolUses, ?cancellationToken = cancellationToken)
| Choice2Of2 tcSymbolUses ->
#if FABLE_COMPILER
seq {}
#else
Async.RunSynchronously(tcSymbolUses, ?cancellationToken = cancellationToken)
#endif //!FABLE_COMPILER

[|
for r in tcSymbolUses do
Expand Down
23 changes: 22 additions & 1 deletion tests/Js/Main/AsyncTests.fs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module Fable.Tests.Async

open System
open Util
open Util.Testing

#if FABLE_COMPILER
Expand Down Expand Up @@ -72,6 +73,26 @@ let tests =
!result
f true + f false |> equal 22

testCase "Non captured exception in async is propagated when using Async.StartImmediate" <| fun () ->
throwsAnyError (fun _ ->
async {
failwith "boom!"
} |> Async.StartImmediate
)

#if FABLE_COMPILER
// Behaviour of Async.Start, is the same as Async.StartImmediate in JS
// We disable this test for .NET, because it seems like we can't capture the exception
// This should be fine, because Fable generate a warning about the Async.Start
// behaviour being the same as Async.StartImmediate
testCase "Non captured exception in async is propagated when using Async.Start" <| fun () ->
throwsAnyError (fun _ ->
async {
failwith "boom!"
} |> Async.Start
)
#endif

testCase "Simple async is executed correctly" <| fun () ->
let result = ref false
let x = async { return 99 }
Expand Down Expand Up @@ -596,4 +617,4 @@ let tests =
let! res = parentWorkflow()
equal 7 res
}
]
]

0 comments on commit 9250574

Please sign in to comment.