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