Here is a client side Fable.Remoting example that prints the result of an async function.
// Client code (Compiled to Javascript using Fable)
// ============
open Fable.Remoting.Client
let server = Proxy.create<IServer>
async {
let! length = server.getLength “hello”
do printfn “%d” length // 5
}
|> Async.StartImmediate
How do I get the length value?
I see you've tagged your question with elmish, so I'm going to assume you have a Msg type defined. Don't use Async.StartImmediate or Async.RunSynchronously; in Elmish, you should use Cmd.OfAsync to schedule a message to be dispatched once the async block returns a value. There are four functions in Cmd.OfAsync (and the same four appear in Cmd.OfPromise as well): either, perform, attempt, and result. I'll break them down for you since their documentation isn't quite up to snuff yet:
either: takes four parameters, task, arg, ofSuccess, and ofError. task is the async function you want to call (of type 'a -> Async<'b>). arg is the parameter of type 'a that you want to pass to the task function. ofSuccess is a function of type 'b -> 'Msg: it will receive the result of the async function and is supposed to create a message, presumably one that incorporates the 'b result. Finally, ofError is a function of type exn -> 'Msg: if the task function throws an exception, then ofError will be called instead of ofSuccess, and is supposed to turn that exception into an Elmish message that your code can handle (presumably one that will log an error to the Javascript console or pop up a notification with Thoth.Toast or something like that).perform: like either but there's no ofError parameter. Use this if your async command cannot fail (which is never the case with remote API calls, as it's always possible the network is down or your server is unresponsive), or if you just don't care about exceptions and don't mind an unhandled exception getting thrown.attempt: like either but there's no ofSuccess parameter, so the task function's result will be ignored if it succeeds.result: this one is completely different. It just takes a single parameter of type Async<'Msg>, i.e. you pass it an async block that is already going to produce a message.With the code you've written, you would use Cmd.OfAsync.result if you wanted to make minimal changes to your code, but I would suggest using Cmd.OfAsync.perform instead (and upgrading it to Cmd.OfAsync.either once you have written some error-handling code). I'll show you both ways:
type Msg =
// ... rest of your messages go here
| GetLength of string
| LengthResult of int
let update msg model =
match msg with
// ... rest of your update function
| GetLength s ->
let usePerform = true
if usePerform then
model, Cmd.OfAsync.perform server.getLength s LengthResult
else
let length : Async<Msg> = async {
let! length = server.getLength s
return (LengthResult length)
}
model, Cmd.OfAsync.result length
| LengthResult len ->
// Do whatever you needed to do with the API result
printfn "Length was %d" len
model, Cmd.none
And if you were using either (which you really should do once you go to production), there would be a third message LogError of exn that would be handled like:
| LogError e ->
printfn "Error: %s" e.Message
model, Cmd.none
and the Cmd.OfAsync.perform line in the code above would become:
model, Cmd.OfAsync.either server.getLength s LengthResult LogError
That's the right way to handle async-producing functions in Elmish.
Async is one of the places where you use return in F#. So you need to return the length value. Also, Async.StartImmediate returns () (unit). Use something else, e.g. Async.RunSynchronously if you need the extracted value. Depends on what you need to achieve with it.
let length =
async {
let! length = async {return String.length "hello"}
do printfn "%d" length // 5
return length
} |> Async.RunSynchronously
length // val it : int = 5
Btw, you mention fable. So you might be able to use JS promise.
Some resources on Async in F#:
F# Async Guide from Jet
Async Programming
FSharp for Fun and Profit
Microsoft Docs
C# and F# Async
For those who want to call from js code.
// Client code (Compiled to Javascript using Fable)
// ============
open Fable.Remoting.Client
open Fable.Core // required for Async.StartAsPromise
let server = Proxy.create<IServer>
let len_from_fable () =
async {
let! length = server.getLength “hello”
return length
} |> Async.StartAsPromise
call from js
async func() {
let len = await len_from_fable()
print(len)
}
works in fable 3.0.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With