Fable: Create Async from function requiring callback

Created on 21 May 2016  路  14Comments  路  Source: fable-compiler/Fable

I'm trying to create a wrapper function that returns an XMLHttpRequest as an async. At the moment, it is function requiring a callback.

send: url:string -> request:Request -> settings:Settings -> (Result<'a, 'b> -> unit)

send: url:string -> request:Request -> settings:Settings -> Async<Result<'a, 'b> >

Is there a way to convert this to an async that returns Result<'a, 'b>?

bug

All 14 comments

Async.FromContinuations work. However the callback function is 't -> unit instead of f('t ->unit, 'exn -> unit, cExn->unit). Is this by design?

It sounds like you are in the same space as I am with issue #146. Excelpt that I can't get FromContinuations to work with StartImmediate. Do you have some working code?

It is working for me. I used it with Start but the docs say at the moment, it has the same effect as StartImmediate.

//somewhere in my Http.fs file
let sendAsync settings request =
  Async.FromContinuations (fun f->
    let callback = unbox f 
    send settings request callback
    |> ignore
  )

//in my Essentials.fs file
module CommandProcessor =
  let processCmd msgHandler cmd =
    match cmd with
    | Cmd.None -> ()
    | Cmd.Value v -> msgHandler v
    | Cmd.Function f -> msgHandler <| f()
    | Cmd.AsyncFunction (task:Async<'a>) ->
        async {
          let! x = task
          msgHandler x
        } |> Async.Start

OK, I'm probably being really dim, but where in your example are you actually using SendAsync after you define it? I assume you're calling processCmd with some parameters involving SendAsync?

I'm trying to recreate some kind of messaging structure for an app I'm porting. The effects are presented as Commands which are then processed. One of the cases for the commands is an Async<'a> which you can see from processCmd above.

This is the usage

//in HttpBuilder.fs
let send successReader errorReader (RequestBuilder (request,settings)) =
  Http.sendAsync settings request
  |> AsyncUtils.mapError promoteRawError
  |> flip AsyncUtils.andThen (handleResponse successReader errorReader)

//in Trello.fs
let private post url content =
  HttpBuilder.post url
  |> withMultipartStringBody content
  |> send stringReader stringReader
  |> AsyncUtils.mapError parseError
  |> AsyncUtils.map (fun resp -> resp.data)

let addCardAsync auth card =
    post (buildUrl auth "cards") (cardAsStringList card)


//in FeedBackPanel.fs
... in an update function
let fx = Trello.addCardAsync context.trello card
          |> AsyncUtils.toCmd SaveComplete 

member x.onMessage msg =
  let state,cmd =
    update msg x.state
  x.setState state
  processCmd x.onMessage cmd //this is where process is called
  x.props.observers |> List.iter (fun observer -> observer msg)

I'm using async to carry my tasks around so that their execution can be controlled. Just having a simple implementation though.

Exactly what do you want to achieve so we look at some specific code?

I'm trying to work out how best to wrap existing javascript libraries with callbacks into nice async functions I can call in an f# style. The specific case I'm using is trying to get an sql query as a response in an express api.

So the following code works fine - using the mysql callback function:

app.get(U2.Case1 "/api/sqlcallback",(fun req res _ ->
    let connectionConfig:IConnectionConfig = createEmpty<IConnectionConfig>
    connectionConfig.user <-Some  "gary" /...etc for other parameters
    let connection = mysql.Globals.createConnection(connectionConfig)
    connection.connect()
    let callback (err:IError) (result:obj) =
      res?json $ result |> ignore
    connection.query.Invoke( "select * from mytable" ,Func<IError,obj,unit> callback ) |> ignore
    box() 
    )) |>ignore

So I try to turn this into async and do this:

let asyncgetSql  ( query: string ) = 
    let connectionConfig:IConnectionConfig = createEmpty<IConnectionConfig>
    connectionConfig.user <-Some  "gary"
    let connection = mysql.Globals.createConnection(connectionConfig)

    connection.connect()
    Async.FromContinuations (fun(r,e,x)->
      connection.query.Invoke(query,fun err result->
           r result // this line crashes at runtime because r is undefined
          ) |> ignore
     )


// now call the above within an asnyc block to respond to an express request
app.get(U2.Case1 "/api/sqlasync",fun req res _ ->
    async {
    let! queryResult = asyncgetSql "select * from mytable"
    res?json $ queryResult |> ignore 
    } |> Async.StartImmediate
    box ()
)  |>ignore

But whereas the first version works fine, the second crashes when it's trying to call the continuation
function, because it's undefined.

So I'm trying to understand if this is me being silly, or a bug.
Meanwhile, I've tried various other ways of running FromContinuations using StartImmediate, and they all either fail to compile or crash...

Hope that clarifies my issue!
Thanks

Hi there! Thanks a lot for the discussion and sorry to keep you waiting 馃槄 I finally gave some love to the Async module: I fixed several issues (including the callback of FromContinuations which was indeed wrong) and added most of the missing methods, including StartWithContinuations, Parallel, AwaitPromise and StartAsPromise.

Could you please give the new version a try and confirm if it's working for you? Please note you have to update both fable-compiler to v0.3.0 and fable-core to v0.1.0. If you still find issues or have any comment please do not hesitate to contact back.

Have fun!

Thanks Alfonso,
My code now works unchanged as expected. Will now continue to test things including the other parts of the async api and will raise new issues as appropriate.

Good stuff!

Updated the node packages and changed my code from

    async {
      let! x = task
      msgHandler x
    } |> Async.StartImmediate

to

    Async.StartWithContinuations (task,msgHandler,ignore,ignore)

It get the error

Cannot find replacement for Microsoft.FSharp.Control.Async.startWithContinuations (L41,8)-(L41,68)

What am I missing? Switching to the old one works though.

@ritcoder, seems fable-compiler is not updated yet. I sometimes get confused on how to tell npm to update packages to their latest version, but with global installations, usually npm install -g fable-compiler does the trick. Can you make sure you're using v0.3.0? You can check the version by typing fable --help.

It was still 0.2.14. I executed npm install fable-compiler and it was still the same. After an uninstall and an install, it was uped to v0.3.0. I still got the same error though.

If you're using npm install fable-compiler (without the -g option) it means you're installing the compiler locally (in the node_modules folder of the current directory). For local dependencies, npm follows semantic versioning for updates. The only way to ensure you'll always have the latest version is to use "fable-compiler": "*" in package.json.

Maybe there's a confusion with the local and global installations? Could it be that you're installing the latest version locally but then running the global command which is not updated yet? Possible solutions:

To update and run fable globally:
1) npm install -g fable-compiler (mind the -g)
2) fable (in the dir where fableconfig.json is)

To update and run fable locally:
1) npm install fable-compiler (no -g, check fable-compiler version in package.json)
2) node node_modules/fable-compiler (in the dir where fableconfig.json and package.json are)

Also, check the version of fable-core in package.json. I had to update that too when I tested things earlier today.

Thanks. Turn out I updated the local copy and not the global one. After updating package.json and doing a global install, it worked.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

sandeepc24 picture sandeepc24  路  46Comments

alfonsogarciacaro picture alfonsogarciacaro  路  26Comments

alexfoxgill picture alexfoxgill  路  39Comments

alfonsogarciacaro picture alfonsogarciacaro  路  30Comments

matthid picture matthid  路  29Comments