5

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#.

1 Answer 1

7

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.

Sign up to request clarification or add additional context in comments.

2 Comments

- Thanks, that code is MUCH nicer - and thanks for the understanding. does it always need to be a Task or are there other ways of doing it?
@MartinThompson You can convert any tasks to asyncs as well and use the async builder, but if you then need to change it back to a task at some point anyway then it's better to stick with tasks all the way through. It all depends on what happens with the output of this function.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.