Вернуть асинхронное значение из Fable.Remoting

Вот пример Fable.Remoting на стороне клиента, который печатает результат асинхронной функции.

    // 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

Как я могу получить length стоимость?

3 ответа

Решение

Я вижу, вы пометили свой вопрос на Elmish, поэтому я предполагаю, что у вас есть Msg тип определен. Не использовать Async.StartImmediate или же Async.RunSynchronously; в Elmish, вы должны использовать Cmd.OfAsyncзапланировать отправку сообщения, когда асинхронный блок возвращает значение. Есть четыре функции вCmd.OfAsync(и те же четыре появляются вCmd.OfPromise также): either, perform, attempt, а также result, Я сломаю их для вас, так как их документация еще не совсем готова:

  • either: принимает четыре параметра,task,arg, ofSuccess, а также ofError, taskэто асинхронная функция, которую вы хотите вызвать (типа 'a -> Async<'b>). arg это параметр типа 'aчто вы хотите передать task функция. ofSuccess является функцией типа 'b -> 'Msg: он получит результат асинхронной функции и должен создать сообщение, предположительно такое, которое включает 'b результат. В заключение, ofErrorявляется функцией типа exn -> 'Msg: если task функция выдает исключение, затем ofErrorбудет называться вместо ofSuccess и должен превратить это исключение в эльмийское сообщение, которое может обработать ваш код (предположительно, такое, которое сообщит об ошибке в консоль Javascript или отобразит уведомление с Thoth.Toast или чем-то в этом роде).
  • perform: любить either но нет ofError параметр. Используйте это, если ваша асинхронная команда не может потерпеть неудачу (что никогда не случается с удаленными вызовами API, поскольку всегда возможно, что сеть отключена или ваш сервер не отвечает), или если вы просто не заботитесь об исключениях и не возражаете против необработанное исключение становится брошенным.
  • attempt: любить either но нет ofSuccess параметр, поэтому task результат функции будет проигнорирован в случае успеха.
  • result: это совершенно другое. Это просто принимает один параметр типа Async<'Msg>то есть вы передаете async блок, который уже собирается выдать сообщение.

С кодом, который вы написали, вы бы использовали Cmd.OfAsync.result если вы хотите внести минимальные изменения в свой код, но я бы предложил использовать Cmd.OfAsync.perform вместо этого (и обновляя его до Cmd.OfAsync.either как только вы написали некоторый код обработки ошибок). Я покажу вам оба пути:

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

И если вы использовали either (что вы действительно должны сделать, как только вы идете в производство), будет третье сообщение LogError of exn это будет обработано как:

    | LogError e ->
        printfn "Error: %s" e.Message
        model, Cmd.none

и Cmd.OfAsync.perform строка в коде выше станет:

        model, Cmd.OfAsync.either server.getLength s LengthResult LogError

Это правильный способ обработки асинхронных функций в Elmish.

Async является одним из мест, где вы используете return в F#. Так что вам нужно вернуть значение длины. Также, Async.StartImmediate возвращается () (единица измерения). Используйте что-то еще, например Async.RunSynchronously если вам нужно извлечь значение. Зависит от того, что вам нужно достичь с ним.

let length = 
    async {
         let! length = async {return String.length "hello"}
         do printfn "%d" length // 5
         return length
        } |> Async.RunSynchronously

length // val it : int = 5

Кстати, вы упоминаете басню. Таким образом, вы можете использовать JS promise,

Некоторые ресурсы по Async в F#:

F# Async Guide от Jet

Асинхронное программирование

FSharp для удовольствия и прибыли

Документы Microsoft

C# и F# Async

Для тех, кто хочет звонить из js кода.

      // 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

звонок из js

          async func() {
        let len = await len_from_fable()
        print(len)
    }

работает в басне 3.0.

Другие вопросы по тегам