Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Simplify Asynchronous Programming in F#

I come from a C# background having used async/ await. I am trying to find a "less verbose" way of programming using a library. ( specifically the Microsoft Playwright library for browser automation )

let (~~) = Async.AwaitTask
let getLastPageNumber(page: IPage) = 
    let playwright = ~~Playwright.CreateAsync() |> Async.RunSynchronously
    let browser = ~~playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions(Headless = false )) |> Async.RunSynchronously
    ~~page.GotoAsync("https://go.xero.com/BankRec/BankRec.aspx?accountID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&page1") |> Async.RunSynchronously |> ignore
    let lastPageLink = ~~page.QuerySelectorAsync("#mainPagerEnd") |> Async.RunSynchronously
    if lastPageLink = null then
        //this is the last page
        1
    else
        let lastPageNumber = ~~lastPageLink.GetAttributeAsync("href") |> Async.RunSynchronously
        lastPageNumber |> int

I have shortened things a bit using the alias ~~ for Async.AwaitTask but it seems to be a lot of code to do something that was a lot easier in C#.

like image 489
Martin Thompson Avatar asked Oct 05 '21 05:10

Martin Thompson


1 Answers

Async.RunSynchronously should only be used as a very last resort because it blocks a thread to perform the computation, which defeats the purpose of using async/tasks.

The F# equivalent of C#'s async/await is to use F#'s Async type, and the async computation expression. However, if you're using a .NET library which uses the .NET Task type then you can use the TaskBuilder.fs library which has a task computation expression.

Then you would write the function like this:

open FSharp.Control.Tasks

let getLastPageNumber(page: IPage) =  task {
    let! playwright = Playwright.CreateAsync()
    let! browser = playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions(Headless = false ))
    let! _ = page.GotoAsync("https://go.xero.com/BankRec/BankRec.aspx?accountID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&page1")
    let! lastPageLink = page.QuerySelectorAsync("#mainPagerEnd")
    if lastPageLink = null then
        //this is the last page
        return 1
    else
        let! lastPageNumber = lastPageLink.GetAttributeAsync("href")
        return lastPageNumber |> int
}

Inside the computation expression (also called a builder) let! is used to await tasks.

Note that this function now returns Task<int> rather than int, so the caller would probably need to follow a similar pattern and propogate the task further.

You can read more about using async in F# here. Some of that knowledge can also be applied to the task computation expression.

like image 195
TheQuickBrownFox Avatar answered Oct 29 '22 09:10

TheQuickBrownFox