Search code examples
asynchronousf#try-with

F# issue with async workflow and try/with


I am having a headache trying to put together a simple functionality.

Consider the following definitions:

type Entity = {Id:int;Data:string}

type IRepository =
  abstract member SaveAsync: array<Entity> -> Task<bool>
  abstract member RollBackAsync: array<Entity> -> Task<bool>

type INotification =
  abstract member SaveAsync: array<Entity> -> Task<bool>

The use Task<T> because they are libraries developed in other .NET languages.

(I have created this code for the sake of the example)

Basically, I want to save data in the repository service, and then save the data in the notification service. But if this second operation fails, and that includes exceptions, I want to rollback the operation in the repository. Then there are two situations where I would want to call the rollback operation, the first if notification.SaveAsync returns false, and the second if it throws an exception. And of course, I would like to code that call to rollback once, but I cannot find the way.

This is what I have tried:

type Controller(repository:IRepository, notification:INotification) =

  let saveEntities entities:Async<bool> = async{

    let! repoResult =  Async.AwaitTask <| repository.SaveAsync(entities)
    if(not repoResult) then
      return false
    else 
      let notifResult =
        try
           let! nr = Async.AwaitTask <| notification.SaveAsync(entities)
           nr
        with
          | _-> false

      if(not notifResult) then
        let forget = Async.AwaitTask <| repository.RollBackAsync(entities)
        return false
      else
        return true
  }

  member self.SaveEntitiesAsync(entities:array<Entity>) =
    Async.StartAsTask <| saveEntities entities

But unfortunately I get a compiler error on the let! nr = ... saying: This construct may only be used within computation expressions

Which would be the right way of doing this?


Solution

  • The problem is that when you use let v = e in computation expressions, the expression e is an ordinary expression that cannot contain further asynchronous constructs. That's exactly what happens here:

    let notifResult =
        try
           let! nr = Async.AwaitTask <| notification.SaveAsync(entities)
           nr
        with _-> false
    

    You can turn this into a nested async block:

    let! notifResult = async {
        try
           let! nr = Async.AwaitTask <| notification.SaveAsync(entities)  
           return nr
        with _-> return false }