Closed isaacabraham closed 5 years ago
Hello @isaacabraham, AFAIK Fable doesn't support Task yet, so it doesn't really make sense to support it yet out of the box and the workaround is as simple as Async.AwaitTask
Yes true. I was thinking more in terms of server-side (i.e. using Task there) but of course that's not possible as we have a typed contract between both sides (which is the whole point of remoting...). Closing :-)
Just wanted to open the same issue. The workaround is easy but it would be nice to just use tasks instead of async...
@Zaid-Ajaj could you please elaborate on this? I personally would prefer to use promise {} on client and task {} on giraffe. Is this not something that would make sense?
@forki The types are shared between client and server, this means both have to understand and know how to work with the shared types. In case of task, the client doesn't understand it as of now and in case of promise, the server won't understand it either. How would you write a int -> Promise
Async however, is something both client and server know how to work with directly. I'd not implement promises on the client either because of their weird error handling model, they can behave unexpectedly because they are like "hot" tasks, executed when created directly.
Ok so the whole computation is cross compiled? I thought it would just cross compile the data transfer type and the computation wrapper would be platform dependent
Zaid Ajaj notifications@github.com schrieb am Sa., 6. Juni 2020, 13:04:
@forki https://github.com/forki The types are shared between client and server, this means both have to understand and know how to work with the shared types. In case of task, the client doesn't understand it as of now and in case of promise, the server won't understand it either. How would you write a int -> Promise on the from giraffe.
Async however, is something both client and server know how to work with directly. I'd not implement promises on the client either because of their weird error handling model, they can behave unexpectedly because they are like "hot" tasks, executed when created directly.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/Zaid-Ajaj/Fable.Remoting/issues/134#issuecomment-640039836, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAOANHLGJJV6HEULO5UBSTRVIPDHANCNFSM4ITRHOLQ .
No not the whole computation, just the types are cross-compiled. Async type is understood by both client and server
In other words someone would have to let Fable know what task { } should be rendered out into on the JS side.
Well I don't really understand why the client would want to know if the internal of the server request is using async or task or returning it sync.
Isaac Abraham notifications@github.com schrieb am Sa., 6. Juni 2020, 17:52:
In other words someone would have to let Fable know what task { } should be rendered out into on the JS side.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/Zaid-Ajaj/Fable.Remoting/issues/134#issuecomment-640081072, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAOANCGNHSWTDLMTMC3PV3RVJQ47ANCNFSM4ITRHOLQ .
In other words someone would have to let Fable know what task { } should be rendered out into on the JS side.
@isaacabraham Yes, but first it has to be supported in F# as a built-in computation expression which is now being implemented.
Well I don't really understand why the client would want to know if the internal of the server request is using async or task or returning it sync.
@forki The client doesn't care at all about server internals. You can use whatever you want: sync, task, async. The client only cares about the definition of the functions being satisfied: always returning Async<'T>
.
When you have some synchronous function, wrap it into async expression. If you have a function that returns Task<'T>
then call Async.AwaitTask
to make it async.
I assume we're talking different things here.
Zaid Ajaj notifications@github.com schrieb am Sa., 6. Juni 2020, 18:20:
In other words someone would have to let Fable know what task { } should be rendered out into on the JS side.
@isaacabraham https://github.com/isaacabraham Yes, but first it has to be supported in F# as a built-in computation expression which is now being implemented.
Well I don't really understand why the client would want to know if the internal of the server request is using async or task or returning it sync.
@forki https://github.com/forki The client doesn't care at all about server internals. You can use whatever you want: sync, task, async. The client only cares about the definition of the functions being satisfied: always returning Async<'T>.
When you have some synchronous function, wrap it into async expression. If you have a function that returns Task<'T> then call Async.AwaitTask to make it async.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/Zaid-Ajaj/Fable.Remoting/issues/134#issuecomment-640084590, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAOANGWEZL5ZY5J74NM5F3RVJUGJANCNFSM4ITRHOLQ .
I can't tell to be honest. This issue is about using Task<'T>
as a return type in function signatures of the protocol (record of functions) definition. Right now all functions defined in the protocol must return Async<'T>
as follows:
type Protocol = {
computeStuff : int -> Async<int * int>
getData : unit -> Async<string>
}
Right now you can't use Task<'T>
as a return type because the client wouldn't know how to work with it but it might be supported in the near future
Yes thanks for clarification. I misunderstood the issue here and confused it with something different that I'm looking for.
Zaid Ajaj notifications@github.com schrieb am Sa., 6. Juni 2020, 18:30:
I can't tell to be honest. This issue is about using Task<'T> as a return type in function signatures of the protocol (record of functions) definition. Right now all functions defined in the protocol must return Async<'T> as follows:
type Protocol = { computeStuff : int -> Async<int * int> getData : unit -> Async
} Right now you can't use Task<'T> as a return type because the client wouldn't know how to work with it but it might be supported in the near future
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/Zaid-Ajaj/Fable.Remoting/issues/134#issuecomment-640085824, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAOANFBHIHSSWIC6TH6QS3RVJVLDANCNFSM4ITRHOLQ .
@isaacabraham Yes, but first it has to be supported in F# as a built-in computation expression which is now being implemented.
@Zaid-Ajaj yeah, I'm agreeing with you - I guess the other solution would be for the TaskBuilder has to be compiled with a Fable version in it like Thoth does?
I guess the other solution would be for the TaskBuilder has to be compiled with a Fable version in it like Thoth does?
Yeah but I believe you would a Fable-specific implementation of TaskBuilder to match runtime expectations of JS and aside from the syntax, it wouldn't really add value to async
in Fable.
offtopic: it will be interesting to see what happens when fable will do when task {} moves into F# lang itself ;-)
@Zaid-Ajaj that's right - but it would mean on the server you could remove all traces of Async.
@Zaid-Ajaj with Task now a core part of F#, do you think it's possible to revisit this? I think it's a worthwhile thing to consider.
There's not really anything that we can do until Fable recognizes tasks that I can think of. Once it does though, the changes in Remoting will be trivial.
Perhaps the simplest thing implementation-wise would be to have tasks identical to asyncs under the hood in JS. Such a decision would have many ramifications (to name one, tasks becoming cold and repeatable, unlike in .NET), so it might not be the best course of action, without very careful consideration, that is.
There's not really anything that we can do
Spoke too soon. The workaround is quite simple if you can stomach the fact that you'll have to put this at the top of your contract files:
#if !TASK_AS_ASYNC && !FABLE_COMPILER
type Async<'a> = System.Threading.Tasks.Task<'a>
#endif
and then define the constant in the client projects.
Let me explore the necessary changes here quick.
@isaacabraham et al.
Tasks are supported in the latest Remoting packages.
Put <DefineConstants>TASK_AS_ASYNC</DefineConstants>
into Client.fsproj and the contract itself should look like
module Contract
#if !TASK_AS_ASYNC && !FABLE_COMPILER
type Async<'a> = System.Threading.Tasks.Task<'a>
#endif
type Api = {
GetTime: unit -> Async<DateTimeOffset>
}
Client code is business as usual with asyncs and on the server you can now use tasks
let api = {
GetTime = fun () -> Task.FromResult DateTimeOffset.Now
}
Use tasks in the contract and server
module Contract
type Api = {
GetTime: unit -> System.Threading.Tasks.Task<DateTimeOffset>
}
On the client you will unfortunately have to manually cast all task invocations to asyncs (unless someone comes up with something clever) or create a wrapper for Cmd.OfAsyncImmediate.perform
and similar functions where you'll do the cast.
let inline coerceTask (t: 'arg -> Task<'result>) = box t :?> 'arg -> Async<'result>
let api =
Remoting.createApi ()
|> Remoting.withRouteBuilder Route.builder
|> Remoting.buildProxy<Api>
type Model = { Time: DateTimeOffset }
type Msg =
| TimeReceived of DateTimeOffset
// let's say you want to call GetTime in an Elmish init method
let init () =
// vvvvvvvvvvvvvvvvvvvvvv
{ Time = DateTimeOffset.MinValue }, Cmd.OfAsyncImmediate.perform (coerceTask api.GetTime) () TimeReceived
@kerams does the Contract file in a separate project scenario
supports Fable.Remoting.Client
? I tried to implement something similar, but I received an error during the execution of Remoting.buildProxy<>
. There is a check that the return type must be Async
. Am I missing something?
Uncaught Error: Expected field GetTime to have a return type of Async<'t>
at eval (String.js?fcca:134:15)
at eval (String.js?fcca:233:20)
at Proxy_proxyFetch (Proxy.fs?836a:137:28)
at normalize (Remoting.fs?8a5c:57:34)
at eval (Remoting.fs?8a5c:93:21)
at eval (Seq.js?ab88:919:100)
at Enumerator_FromFunctions$1.eval [as next] (Seq.js?ab88:272:32)
at Enumerator_FromFunctions$1.SystemCollectionsIEnumeratorMoveNext (Seq.js?ab88:100:19)
at Enumerator_FromFunctions$1.eval [as next] (Seq.js?ab88:197:29)
at Enumerator_FromFunctions$1.SystemCollectionsIEnumeratorMoveNext (Seq.js?ab88:100:19)
at Object.next (Util.js?5e90:63:29)
at Function.from (<anonymous>)
at toArray (Seq.js?ab88:368:22)
at Remoting_buildProxy_64DC51C (Remoting.fs?8a5c:53:32)
Ah, I'm sorry, I use it with binary serialization, but never tested it on the JSON path. Let me try to fix it.
Any plans to support regular .NET tasks in addition to
async
workflows?