diff --git a/src/Fable.Cli/CHANGELOG.md b/src/Fable.Cli/CHANGELOG.md index 23090cffb4..0052595c19 100644 --- a/src/Fable.Cli/CHANGELOG.md +++ b/src/Fable.Cli/CHANGELOG.md @@ -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 diff --git a/src/Fable.Compiler/CHANGELOG.md b/src/Fable.Compiler/CHANGELOG.md index 0fcb281acb..39f1f6a34a 100644 --- a/src/Fable.Compiler/CHANGELOG.md +++ b/src/Fable.Compiler/CHANGELOG.md @@ -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 diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index 80bd99ee91..c6974aaaa5 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -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 @@ -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( diff --git a/src/fable-library-ts/Async.ts b/src/fable-library-ts/Async.ts index 013735fc74..ca7955f618 100644 --- a/src/fable-library-ts/Async.ts +++ b/src/fable-library-ts/Async.ts @@ -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(computation: Async, cancellationToken?: CancellationToken) { - return startWithContinuations(computation, cancellationToken); + return startWithContinuations( + computation, + emptyContinuation, + function (err) { throw err }, + emptyContinuation, + cancellationToken + ); } export function startImmediate(computation: Async, cancellationToken?: CancellationToken) { @@ -173,19 +175,16 @@ export function startImmediate(computation: Async, cancellationToken?: Can export function startWithContinuations( computation: Async, - continuation?: Continuation | CancellationToken, - exceptionContinuation?: Continuation, - cancellationContinuation?: Continuation, + continuation: Continuation, + exceptionContinuation: Continuation, + cancellationContinuation: Continuation, cancelToken?: CancellationToken) { - if (typeof continuation !== "function") { - cancelToken = continuation as CancellationToken; - continuation = undefined; - } + const trampoline = new Trampoline(); computation({ onSuccess: continuation ? continuation as Continuation : emptyContinuation, - onError: exceptionContinuation ? exceptionContinuation : emptyContinuation, - onCancel: cancellationContinuation ? cancellationContinuation : emptyContinuation, + onError: exceptionContinuation, + onCancel: cancellationContinuation, cancelToken: cancelToken ? cancelToken : defaultCancellationToken, trampoline, }); diff --git a/src/fable-library-ts/CHANGELOG.md b/src/fable-library-ts/CHANGELOG.md index e3896cae25..d481ec4da5 100644 --- a/src/fable-library-ts/CHANGELOG.md +++ b/src/fable-library-ts/CHANGELOG.md @@ -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 diff --git a/src/fcs-fable/src/Compiler/Service/FSharpCheckerResults.fs b/src/fcs-fable/src/Compiler/Service/FSharpCheckerResults.fs index 3b4105f1fc..75d613d5aa 100644 --- a/src/fcs-fable/src/Compiler/Service/FSharpCheckerResults.fs +++ b/src/fcs-fable/src/Compiler/Service/FSharpCheckerResults.fs @@ -3860,6 +3860,9 @@ type FSharpCheckProjectResults |> Array.toSeq #endif //!FABLE_COMPILER | Choice2Of2 task -> +#if FABLE_COMPILER + seq {} +#else Async.RunSynchronously( async { let! tcSymbolUses = task @@ -3872,6 +3875,7 @@ type FSharpCheckProjectResults }, ?cancellationToken = cancellationToken ) +#endif //!FABLE_COMPILER results |> Seq.filter (fun symbolUse -> symbolUse.ItemOccurrence <> ItemOccurrence.RelatedText) @@ -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 @@ -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 diff --git a/tests/Js/Main/AsyncTests.fs b/tests/Js/Main/AsyncTests.fs index 4e4044896e..24ddded9cd 100644 --- a/tests/Js/Main/AsyncTests.fs +++ b/tests/Js/Main/AsyncTests.fs @@ -1,6 +1,7 @@ module Fable.Tests.Async open System +open Util open Util.Testing #if FABLE_COMPILER @@ -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 } @@ -596,4 +617,4 @@ let tests = let! res = parentWorkflow() equal 7 res } - ] \ No newline at end of file + ]