Thoughts on async functions, trap and error handling in Motoko

I’d really like to avoid having to design “fallbacks” in my code, let me explain. In a simple scenario like:

func foo() : async () {
  await transfer();

  deliver();
};

If I am not sure that deliver() can or cannot trap, I probably should assume it can trap, and hence design a “fallback” mechanism to save the non delivered services and transfer back the tokens.

Or is there a way to make sure that deliver() cannot trap? Or can we establish a naming syntax for functions that can or cannot trap ?

Let’s continue by assuming deliver() cannot trap.

If now my deliver function does 1) check some conditions, return #err if not satisfied 2) then deliver the service, I still have to deal with my transfer_back fallback.

func foo() : async () {
  if (await transfer()){
    switch(deliver()){
      case(#err) { await transfer_back(); };
      case(#ok) { };
    };
  };
};

One solution would be to just split the deliver function in two, like:

func foo() : async () {
  switch(deliverCondition()){
    case(#err) { };
    case(#ok) { 
      if (await transfer()){
        deliverCore();
      } 
    };
  };
};

But this seems quite wrong to me. I cannot expect another developper working on my code base to call the deliverCondition whenever the deliverCore function is called.

I am trying to think about ways to solve this. I could make a single deliver function that returns two functions (deliverCondition, deliverCore), and then have a function like payService(condition, service) that does :

func payService((condition, service)) : async () {
  switch(condition()){
    case(#err) { };
    case(#ok) { 
      if (await transfer()){
        service();
      } 
    };
  };
};

that I could just call payService(deliver())

Am I trying too hard ? Is there no other way than having a fallback mechanism or splitting the responsabilities of the deliver function in two ?

1 Like

I’ve thought a lot about this too. One way to simplify this is to do away with the async assumptions and just use async data programming where each update function ends in one shot. (This is not an ideal solution, but I think it demonstrates this issue). This paradigm forces you to handle async conditions and simplifies the ability to think about state. You could use this library:

I prefer the await syntax, but it does require strict adherence to watching out for re-entrance and handling failure.

2 Likes